メモ2ブログ

メモtoウェブログ。旧ブログはこちら。 http://sakebook.blogspot.jp/

技術書典7にFitbit本の続編を出した

先日開催された技術書典7にサークル参加しました。サークル参加としては2回目で、技術書典5以来なので1年ぶりの参加でした。

前回の参加記事はこちら

sakebook.hatenablog.com

事前準備(執筆編)

1年ぶりで、結構色々忘れていました。

次の記事を参考に環境を構築しました。去年と大きく異なっていたのは、Re:VIEWのメジャーバージョンが2から3に変わっていました。

mochikoastech.hatenablog.com

続編という位置づけで考えていたのですが、旧刊を購入していないと話がわからないのでは敷居が高すぎると思ったので、一部内容は重複してしまうのですが、それぞれ独立して読めるような構成にしました

今回は表紙をお願いすることにしました。

お願いするにあたって、何が決まっていれば嬉しいのかがわからなかったので、こんな感じだと嬉しいなってのをboothからピックアップして雰囲気を伝えました。

あとは表示サイズと仮タイトルを伝えて、ここはこういうふうにしたほうがFitbitをつけてるのが映えるよーってのをやり取りして決めていきました。

印刷は前回と同じく日光企画さんにお願いしました。Fitbit Versa 2の発売日が技術書展7の明後日なので、なるべく最新の情報を詰めたくて前日まで粘ってたのですが、結局開発者向けの新しい情報は公開されませんでした。

前日印刷でも、午前10時までにデータを入稿すれば間に合いはしますが、印刷代が倍くらいに膨れるので最後の手段と思ったほうが良さそうです。

自分の場合だとA5サイズ64ページで50冊印刷して50,240円でした。

事前準備(ブース編)

前回の振り返りを見て、背の高いPOPスタンドを置いておくと目立つというのと、卓上に布があったほうが圧倒的に見栄えが良いことがわかっていたので、準備しました。

シンプルなあの布POPスタンドを用意しました。

今回は本の価格を1000円にしました。残っていた旧刊も売れればいいなと思い持っていきました。旧刊は500円なので、数枚程度500円玉を用意し、万札にも耐えられるように1000円札を用意しました。

あとはPOPスタンドに吊るす紙をA4で印刷し、電子版も対応していたのでQRコードを印刷した紙を用意しました。

当日

若干遅刻してしまいました。サークル入場も遅れていたので結果的に自分はほとんど待つことなく入場できましたが、準備が間に合わないんじゃないかとヒヤヒヤしました。

本は技術書典のバックアップ印刷所さんに依頼しておけばブースまで持ってきてもらえます。当日着いたらすでに届いていました。

実際の部数は65冊ありました。

遅れたこともあり、立ち読み本の扱いがよくわからなかったのですが横のサークルの方に教えてもらい、無事提出できました。準備はサッとできました。

実は今回も売り子を見つけられず一人での参加となりました。辛かったですがお手洗いなどは横のサークルの方が気を利かせてくれて助かりました。

技術書典5でFitbit本を購入し、今回も購入してくださった方がいてめちゃめちゃ嬉しかったです。

後払いで20部、現金で19部の合計39部売れました。印刷代やサークル参加費を考えると赤字です。旧刊は完売したのでそこは良かったです。余った部数が多かったのでboothにあずけて帰りました。boothでも、月に安定して売上が立たなければ保管料金がかかるみたいなので注意です

今回販売した新刊のboothリンクです。Versa 2買った人や購入検討中の人は買って損のない内容になってます。

sakebook.booth.pm

終わってみて

一人参加は相変わらず辛いですが、ちょっとこなれた感じがあります。ウェアラブルバイスに関するサークルがうちと横のサークルしかなく、横の方はその辺りを話題にだしたりしていたのですが、自分はあまりそういう接客ができていなかったです。交流という意味でも接客という意味でもその辺りはもう少しやりようはあったなーと思いました。

立ち読みはしてくれたのですが、そこから購入に至らなかった人がわりといました。本の質の問題なのか、目次から読み取りにくかったのか、思ってたのと違ったのか、その辺りがわからないのが辛いところです。

掛けた時間と帰ってくるお金は相変わらず見合わないですが、サークル参加自体は敷居が低くなってきているので、気になってる方はやってみると良いと思います。自分の好きなことを好き勝手書いていいんですよ!

参考

技術書典7

BOOTH - 創作物の総合マーケット

株式会社 日光企画

DartでCloud Functions for Firebaseを動かす

最近FlutterでDartを触っているので、Dartでなにかできればなと思いました。

ちょっとした処理をしたくなって、Cloud Functions for Firebaseが使えればなと思い調べたところ、DartJavascriptにトランスパイルしてCloud Functionsで利用できるようにするPluginがあったのでその紹介をします。

firebase_functions_interop

pub.dev

READMEが充実しているのでその通りにやればだいたいできます。

build_runnerでビルドして、生成された.jsファイルをpackage.jsonが見るようにします。

デバッグ

serve コマンドでローカルに立ち上げることができます。

$ firebase serve --only functions

ハマったところ

通信はnode_httpで行う

http.getを行おうとすると実行時に次のようなエラーが発生します。

ReferenceError: XMLHttpRequest is not defined

firebase_functions_interopnode_httpで通信を行なっているので、それに合わせます。

Cloud Functions for Firebaseの無料版ではOutbound networkに制限がある

Cloud Functions for Firebase内でSlackなどとOutbound networkを行うと次のようなエラーログが吐かれます

Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions

無料版だとOutbound networkingがGoogle services onlyという制限があります。

firebase.google.com

解決するにはFlameプランかBlazeプランにする必要があります。

作ったもの

SlackのOutgoing WebHooksを使って、トリガーとなる言葉とユーザグループを指定するとランダムにユーザを選択するものを作りました。

github.com

まとめ

DartでもCloud Functions for Firebaseを動かすことはできますが、適宜制約があるのでハマらないように気をつけましょう。

参考

firebase_functions_interop / Dart Package

node-interop/build_node_compilers at master · pulyaevskiy/node-interop / GitHub

Cloud Functions for Firebase - Billing account not configured / Stack Overflow

Flutterで秘匿情報を扱う

最近Flutterを触ってます。

Gitなどに載せたくない情報の扱い方についてのメモ。2つの方法を取り上げます。

.env + flutter_dotenv

flutter_dotenvを使います。pubspec.ymlに以下を追加して

  • pubspec.yml
dependencies:
  ..
  flutter_dotenv: ^2.0.1
..
flutter:
  ..
  assets:
  - .env
$ flutter pub get

を実行します。

上の例ではプロジェクトルートに .envというファイルを置く想定です。

  • .env
SAMPLE_VALUE=this is dot env value.

main関数のはじめに初期化します。

void main() async {
  await DotEnv().load('.env');
  runApp(DotenvApp());
}

次のように呼び出します。

class DotenvApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Dotenv App"),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              Text(
                'dotenv values: ${DotEnv().env["SAMPLE_VALUE"]}',
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Map<String, String>型なので取り出して適宜キャストして利用します。

一度初期化したらどこからでも呼べます

flutterパッケージに依存しているのでFlutter for Webでは利用できません。

JSON + json_serializable

json_serializableを使います。pubspec.ymlに以下を追加して

  • pubspec.yml
dependencies:
  ..
  json_annotation: ^2.4.0
..
dev_dependencies:
  ..
  build_runner: ^1.0.0
  json_serializable: ^3.0.0
..
flutter:
  ..
  assets:
  - secret.json
$ flutter pub get

を実行します。

上の例ではプロジェクトルートに secret.jsonというファイルを置く想定です。

{
  "value": "secret value",
  "num":  20190606
}

JSONは末尾カンマを許してはくれないので注意しましょう

対応するモデルを作成します。

part 'secret.g.dart';

@JsonSerializable()
class Secret {
  final String value;
  final int num;

  const Secret(this.value, this.num);

  factory Secret.fromJson(Map<String, dynamic> json) => _$SecretFromJson(json);
}

このままだと自動生成されたコードがないので、エラーになります。コードを生成します。

$ flutter pub run build_runner build

これでエラーは消えます。

JSONファイルを読み込んでモデルを返すクラスを用意します。

  • secret_loader.dart
class SecretLoader {
  final String secretPath;
  SecretLoader({this.secretPath});

  Future<Secret> load() {
    return rootBundle.loadStructuredData<Secret>(this.secretPath,
        (jsonStr) async {
      final secret = Secret.fromJson(json.decode(jsonStr));
      return secret;
    });
  }
}

準備はできました。次のように呼び出します。

class SecretApp extends StatelessWidget {
  final SecretLoader loader = SecretLoader(secretPath: "secret.json");

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Secret App"),
        ),
        body: FutureBuilder<Secret>(
          future: loader.load(),
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return Center(
                child: CircularProgressIndicator(),
              );
            }
            final secret = snapshot.data;
            return Center(
              child: Column(
                children: <Widget>[
                  Text(
                    'secret value: ${secret.value}',
                  ),
                  Text(
                    'secret num: ${secret.num}',
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

SecretLoader#loadの返り値がFuture型なのでFutureBuilderを使ってます。

内部で保持する仕組みはないので、その他の変数と同じように呼び出したスコープ内でしか扱えません

flutter_dotenvのときのようにmainで呼び出して、コンストラクタで必要なWidgetに値を渡しても良いと思います。

flutterパッケージに依存していないのでFlutter for Webでも利用できます。その際はJSONファイルをweb/assets/に配置します。

まとめ

どちらも一長一短あります。秘匿情報の利用が初期化時のみで済むのならJSON + json_serializableの方が、若干の準備がありますが良いかなと思いました。構造を持てるのは大きな魅力です。さっと試す程度なら.env + flutter_dotenvのほうが手軽だと思います。

どちらの方法にしても、間違えて秘匿情報を書いたファイルをコミットしないように注意しましょう。

参考

flutter_dotenv / Flutter Package

Storing your secret keys in Flutter – Sócrates Díaz Severino – / Medium

JSON and serialization / Flutter