Flutter 開発で Android Studio から VSCode に移行した

今まで Flutter 開発は Android Studio でやっていたけど、VSCode に移行した。

理由は AI の波が来ていて VSCode がその波に一番のりやすそうだったから。GitHub Copilot とかはライセンスの問題はあれど今後解決されていくだろうし、基本的に VSCode に寄せていくことにした。

VSCode の Flutter 連携もかなり良くできていて、特に困ったことはない。むしろ末尾のセミコロンの自動挿入とかができて大変便利。

やったこと

Plugins

この2つのプラグインを入れる。

.vscode/extensions.json はこの状態。

{
  "recommendations": [
    "dart-code.flutter",
    "dart-code.dart-code"
  ]
}

settings.json

ユーザ設定の settings.json に以下を追記した。末尾セミコロン、import と warning の自動修正を保存時にしてくれる。

  "[dart]": {
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
      "quickfix.insertSemicolon": true,
      "source.organizeImports": true,
      "source.fixAll": true,
    }
  }

.vscode/settings.json

fvm を使っているので以下を追記。

{
  "dart.flutterSdkPath": ".fvm/flutter_sdk",
  "search.exclude": {
      "**/.fvm": true
  },
  "files.watcherExclude": {
      "**/.fvm": true
  },
}

アプリの実行

⌘ + Shift + Dデバッグタブへ移動するので、そこで実行ができる。実行ボタンをポチポチすると 構成の追加 が出るので、そうすると .vscode/launch.json が作成されるので、そこで実行コマンドをいじる。

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "nimotsuireta",
      "request": "launch",
      "type": "dart"
    },
    {
      "name": "nimotsuireta (profile mode)",
      "request": "launch",
      "type": "dart",
      "flutterMode": "profile"
    },
    {
      "name": "nimotsuireta (release mode)",
      "request": "launch",
      "type": "dart",
      "flutterMode": "release"
    }
  ]
}

これで configuration を切り替えて実行ができるようになる。

実行端末の指定

画面下のバーに端末が表示されるので、これをポチポチして実行端末を変更することができる。

おわり

VSCode 移行して半日くらいは戸惑いがあったけど、半日経ったらこっちの方がええやんってなった。

VSCode 最高!

generated なコードに対し Riverpod generator で生成すると dynamic 型になってしまう

cloud_firestore_odm で生成された型を riverpod generator で利用したかったがダメっぽい。これは firestore がどうこうではなく、generated されたコードに対し generator を使うことができないっぽい。

まず以下のコードで FeedQuerySnapshot が生成される。

@Collection<User>('users')
@Collection<Feed>('users/*/feeds')
final usersRef = UserCollectionReference();

riverpod generator で以下のように書く。

@riverpod
Stream<FeedQuerySnapshot> feeds(FeedsRef ref) {
  final uid = ref.watch(authStateProvider).value?.uid;
  return usersRef.doc(uid).feeds.snapshots();
}

そうするとこのように dynamic な型として生成されてしまう。

@ProviderFor(feeds)
final feedsProvider = AutoDisposeStreamProvider<dynamic>.internal(
  feeds,
  name: r'feedsProvider',
  debugGetCreateSourceHash:
      const bool.fromEnvironment('dart.vm.product') ? null : _$feedsHash,
  dependencies: null,
  allTransitiveDependencies: null,
);

これにより実行時エラーとなってしまう。

これはどうしようもなさそうで、手で Provider を書く必要がありそう。

Using generated code as input of other generators doesn't work.

github.com

業務でうまくいかなくても謝らないで毅然としていてほしい

何か業務でうまくいないことがあった時、「すみません」とか「🙇」などを使って謝罪を表明せずに胸を張っていてほしい。

業務で失敗することなんて無限にあるし、「ああ迷惑をかけてしまったな」と思うことがあったとしても、基本的には謝罪は不要でシャキッとしていてほしい。謝りたくなる気持ちはわかるが、むしろ「俺悪くないけどね!」「みんなも気をつけようね!」くらいの態度でいてほしい。

でも、なぜ謝罪してほしくないのかはうまく言語化できない。なんでそう思うようになったんだろう?

自分も若いうちはよく🙇を使ってた気がするけど、よくないなと思って最近は意図的に使わないようにしている。一方で🙏は頻繁に使う。「すみません」は使わないけど「申し訳ないのですが〜」は使う気がする。

この差がなんなのかよくわからない。

はじめるNotion 使いかたを自由にデザインするための、基本、コツ、アイデア 読書感想文

Notion 使い始めたけど、雰囲気で使っており使いこなせている感がなかったので読んでみた。
本買わなくてもググったり公式のチュートリアル見たりしたら良さそうだけど、まあ本で体系的に学ぶかと思って読んだ。

感想として、まあ買う必要性は低かったかなと思う。当初の雰囲気で使っていた感じからは若干脱せた気はする。一応学んだこととしては、データベースをたくさんのviewで見れたりリレーションの使い方などは本で知った。他のことは雰囲気で触って大体知っていた。

Notion みたいな wiki っぽいツールを全然使ったことがない人が入門として読むのは良さそう。

住所+店舗名から緯度経度を取得する

ここ最近、店舗を地図にマッピングするアプリをいくつか作った。

千埼神割

千埼神割

  • Kensuke Hoshikawa
  • 旅行
  • 無料
apps.apple.com

apps.apple.com

このアプリを作った時、住所+店舗から緯度経度を収集した。ただの住所ではなく、個別の店舗の緯度経度となる。ややこしいのは住所の緯度経度と店舗の緯度経度は微妙にずれることである。

店舗の緯度経度を求めるには Google Map の Geocoding API でやるのが一番良さそうだが Google Map API は有料になる(この記事を書きながら再度調べたら無料枠があるっぽく、枠内で収まるならそれが良さそう)。

無料で住所から緯度経度を引くことを考えると、まず国土地理院APIが出てくる。しかし国土地理院APIだと店舗名に対応していない。ショッピングモールのように同じ住所にたくさんの店舗がある場合だと全て同じ緯度経度となってしまう。そこで Geocoding.jp という謎のAPIがあり、このAPIを利用すると Google Map のように店舗ごとに緯度経度を取得することができる。Geocoding.jp は裏側は Google / Yahoo となっているっぽい。ただ Geocoding.jp はほとんどの住所+店舗名から緯度経度をひけるのだが、一部取得できないことがある。

そこで、まず Geocoding.jp で緯度経度を取得し、取得できなかったら国土地理院から取得するようにした。

実装

TypeScript で実装した

Geocoding.jp から緯度経度を取得

  • Geocoding.jp は10秒間隔をあけてAPIを叩くこと
  • Geocoding.jp は緯度経度が0で返ってくることがあり、その場合はリトライで対応する
  • レスポンスが XML なので xml2js を使った
const xmlToJson = async (xml: string) => {
  const options = {
    trim: true,
    explicitArray: false,
  };

  return (await xml2js.parseStringPromise(xml.replace(/&/g, "&amp;"), options))
    .result;
};

const getLatlngByGeocoding = async (
  address: string,
  name: string
): Promise<{ lat: number; lng: number }> => {
  await setTimeout(10000);
  const url = `https://www.geocoding.jp/api/?q=${address} ${name}`;
  const response = await fetch(url);
  const text = await response.text();
  const json = await xmlToJson(text);

  // 取得できなかったら国土地理院から取得
  if (
    json.coordinate === undefined ||
    json.coordinate.lat === undefined ||
    json.coordinate.lng === undefined
  ) {
    return getLatlngByGsi(address);
  } else {
    // 0 の時はリトライ
    if (json.coordinate.lat === "0" || json.coordinate.lng === "0") {
      return getLatlngByGeocoding(address, name);
    } else {
      return {
        lat: parseFloat(json.coordinate.lat),
        lng: parseFloat(json.coordinate.lng),
      };
    }
  }
};

国土地理院 API から緯度経度を取得

const getLatlngByGsi = async (
  address: string
): Promise<{ lat: number; lng: number }> => {
  const url = `https://msearch.gsi.go.jp/address-search/AddressSearch?q=${address}`;
  const response = await fetch(url);
  const text = await response.text();
  const coordinates = JSON.parse(text)[0].geometry.coordinates;
  return { lat: parseFloat(coordinates[1]), lng: coordinates[0] };
};

なお、緯度経度が重複した場合はアプリ上でピンが重なってしまうため、ちょっとだけ緯度経度をずらして重複がなくなるようにする必要がある。

エンジニアリング組織論への招待 読書感想文

エンジニアリング組織論への招待という本が評判良さげで読んでみた。

不確実性にどう対処していくか、という点に焦点が当てられていた。不確実性とはわからないこと、わからないことは未来と他人で、そのわからないことを少しでも減らしていくことが大事。

ペアプロが上手くいくと2人がバラバラでプログラミングを行うよりも高い生産性が出るってあってマジ?ってなった。じゃあみんなペアプロした方がいいじゃん、少なくとも毎日1時間くらいペアプロするのは全然良さそうだなあと思う。

メンタリングについては雰囲気でやっていたのでためになった。ちゃんと相槌したり、リフレーミングして解けない問題を解けるように導くとか。ストーリーテリングは自慢話にならないように自分の経験を相手に伝えるということで、自分語りは積極的にしないようにと思ってたけど必要なら話した方が良さそう。

本の内容はすごく良くて、何よりこの内容を言語化しているのがすごい。この本の著者が EM.FM という Podcast をやっていてそれもとても面白くて最近過去回も聴いている。

podcasts.apple.com

書籍にもあるようなことをEM.FMでも話していて、リーダは意思決定をすることが重要でサーバント的な動きはその次だみたいなことをいってて刺さった。前職で一時期リーダ的な職についていたけどあまり意思決定的なことはできてなかったなあ。
また、目標設定の回も良くて、目標設定は評価(給与)のために仕方なくやるものだなあとダラダラやっていたけど、セルフコントロール(成長)のためと言ってて確かにそうだったなあと思った。でも目標設定は自分を成長されるためにあるって誰かに説明されたことある? ない気がする。
心理的安全性についてもこの本でもEM.FMでも話になっていて、弱みを見せたり問題点を指摘できる状態、つまり対人リスクを取ることができる状態というのが心理的安全のある状態となる。今の業務のチームでもできてないかも。

現職はマネジメント職的なのがいないホラクラシー的な組織になっており、開発ばかりやっているがマネジメント的なことはしてないのだが、それでもたくさんためになることがあって良かった。

Flutter でスクロールするとヘッダー背景がいい感じになるやつ

Flutter には SliverAppBar というものがあり、これを使うとヘッダーの AppBar をいい感じに表示することができる。

Twitter iOS アプリのプロフィールページなどのように、PullToRefresh するとヘッダー画像が blur していい感じに表示される、というのも SliverAppBar で簡単に実装できる。

なのだが、ちょっと複雑なことをしようとするとうまくいかず、自力で ScrollController の offset を取得してヘッダー背景がいい感じになるような Widget を作った。やってることは難しくなくてスクロールの offset をみて画像の位置をいじったり AppBar の opacity を変化させている。(iOS アプリ開発の時によくやっていたので、モバイルアプリとしてはあるあるな実装だと思う)

これで一応動いているのだが、スクロールのたびに Widget の更新が走るため、パフォーマンスは不安である。

こんな感じで動く。

class HeaderScrollPage extends StatefulWidget {
  const HeaderScrollPage({Key? key}) : super(key: key);

  @override
  HeaderScrollPageState createState() => HeaderScrollPageState();
}

class HeaderScrollPageState extends State<HeaderScrollPage> {
  double _appBarOpacity = 1.0;
  double _transform = 0.0;
  double _backgroundHeight = 200;
  final _scrollController = ScrollController();
  final _topMargin = 80;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(
      () {
        final offset = _scrollController.offset;
        setState(() {
          _transform = offset < 0 ? 0 : -offset;
          _backgroundHeight = offset < 0 ? 200 - offset : 200;
          _appBarOpacity = max(((_topMargin - -_transform)), 0) / _topMargin;
        });
      },
    );
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: _appBarOpacity == 0.0
          ? null
          : AppBar(
              foregroundColor: Colors.black.withOpacity(_appBarOpacity),
              backgroundColor: Colors.transparent,
              title: const Text('AppBar'),
            ),
      body: Stack(
        children: [
          Container(
            transform: Matrix4.translationValues(0.0, _transform, 0.0),
            height: _backgroundHeight,
            width: double.infinity,
            child: Image.network(
              // https://min-chi.material.jp/fm/bg_c/facility2/ をお借りしています
              'https://min-chi.material.jp/mc/materials/background-c/facility2/_facility2_1.jpg',
              fit: BoxFit.cover,
            ),
          ),
          Center(
            child: SingleChildScrollView(
              controller: _scrollController,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  SizedBox(
                    height: _topMargin + 20,
                  ),
                  const SizedBox(
                    width: 200,
                    height: 200,
                    child: CircleAvatar(child: Text('Avatar')),
                  ),
                  const SizedBox(height: 40),
                  Column(
                    children:
                        List.generate(100, (index) => Text("Text $index")),
                  )
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

ChatGPT に Flutter の scrollcontroller の使い方を教えて って聞いたらただ動くだけじゃなく dispose ちゃんとしろよって教えてくれた。flutter で 100この Text widget を生成して って聞いたらちゃんと出力してくれて便利。

シリコンバレーのエンジニアはWeb3の未来に何を見るのか 読書感想文

私は Web3 のことをよく知らないが、利用者を騙すような形で金儲けしているように思っていて冷ややかな目で見ていたが、著名な方が Web3 に関して本を出していたので読んでみた。

感想としては、Web3 に関して理解が深まったのと、データや履歴が永続化してスマートコントラクトで自動で処理が動くという未来はまあ妥当そうと思った。 電子書籍をNFTにしたら中古売買できたり、政府の金の流れをブロックチェーンにして明確化させたりというのは未来としてありそう。なのだが、出版業回や政府はそんな都合の悪いことをしないだろうとも思うし、現実世界としてそれらが実用化されないのではないか。

Web3 に関して私が抱いていた「悪どい金儲け手法」について書籍で何度も良くないこととして記述されており、やっぱり現状の Web3 として収益化みたいなのを考えるとポンジスキームみたいになってしまっているのは課題のようだ。NFT も思想は理解できるが所有権に対し高額な取引が行われているのはあまり実感が湧かない。これは老害的な考えで、将来は NFT が基本になっているのかもしれない。

Web3 の可能性は理解しつつも現実世界にはなかなか浸透しないのではないか、という感想になった。

10年戦えるデータ分析入門 を読んだ

今更だけど尊敬している先輩が書いた本を読んだ。よかった。

内容はそんな難しくなくて、ある程度知っていることも多かった。今までは雰囲気で分析SQLを書いていたので group by とウィンドウ関数に苦手意識があったがそれが減ったのと、今後分析するときにこの本に還ってくれば良いという安心感を得られた。
セルフジョインやクロスジョイン、スカラーサブクエリーなどは記憶になかったので知れてよかった。

私は分析基盤を構築することはあまりないかなあと思っているので、後半の構築や運用の話は流し読みした。でもその中でジョブ管理システムが出てきて、新卒の時にWindows98みたいな見た目のジョブ管理システム(たぶんJP1だったと思う)を使っていたことや、泣きながらSybaseのストアドプロシージャを書いていたことを思い出し懐かしい気持ちになった。

以下雑メモ:

  • ランダムサンプリング
    • where random() < 0.001 みたいにしてデータからランダムに何件か取得する
  • group by の絞り込みは having
  • interval xx時間前後
  • extract 年や月、日を取り出せる
  • date_trunc 2014-09-28 12:34:562014-09-01 00:00:00 みたいにできる
  • セルフジョイン
    • 自分自身とジョインする
    • 1年前のデータとジョインして伸び率を出すなど
  • クロスジョイン
    • 全ての組み合わせを生成する
    • 縦持ち横持ちテーブルの変換など
  • スカラーサブクエリー
    • select や where で利用できるサブクエリ
  • ウィンドウ関数
    • group by で作ったグループの中を皆がなら集約をせずに計算できる
      • 普通に group by すると結果が1行になってしまうが、ウィンドウ関数を利用するとその結果を元の行に追加できる
    • rank で順位を出せる
    • rank は同じ値では重複してしまうので、row_number でソート順に順位をつけれる
    • sum は over をつけるとウィンドウ関数としても動く
    • ウィンドウフレームは計算対象範囲を狭めることができる
      • rows between 前 and 後
    • ntile デシル分析ができる
    • avg移動平均計算ができる
  • view はテーブルのように見えるが使われるたびに select を実行する create view hoge as select ...
    • with でもいけそう?
  • case で条件分岐 case 式 X when 式1 then 結果1 ... else 結果E end
  • PostgreSQL には json_to_record 関数があり json を変換できる
  • セッション分析
    • lag ウィンドウ関数を使って前の行の値を取ることができる
  • 分析SQLにおいてはSQLのindexはほぼ効果がないが、論理パーティションという日や月毎に切ったテーブルを用意しておくのは効果的

イシューからはじめよ を読んだ

Audible を無料体験で始めてみて、なんとなく興味があったこの本をまず Audible で聴いて、その後 Kindle で読んだ。最初は Audible で聞けばいいやと思ったんだけど、Audible だと全然頭に入ってこなかった。でも Audible で雰囲気は掴めたので Kindle では割とサクサク読めた。
Audible で流し聴きして後で Kindle で読むのが効率的に読めるかもしれない。

高い生産性を出すには「何に答えを出すべきなのか」というイシューを見極めることが大事で、まず最初のステップでイシュー度の高い問題を絞り込み時間を浮かせることが不可欠である。その逆の、時間をかけてなんとすることを犬の道と言っており、それは結構心当たりがあるなあと思ってしまった。なんか頭がうまく働かない時などは手をつけられるところからだらだら始めて時間をかけてしまう。そうではなく、最初にちゃんとイシューを見極めるのが大事である。インパクトのある問いが良いイシューではなく、ある人にとってはイシューであっても別の人にとってはどうでも良いものだったり、答えを出せる見込みがないものは良いイシューとは言えない。自分なら答えを出せる、という視覚的なイシューを見つけることがイシュー見極めの理想。
最後に何が欲しいのか、という逆から考えてイシュー分解もできる。ダブりも漏れもなく、という考え方をMECEといい、検証を行うときはこれが大事。

また、問題を解決するときに情報を集めすぎないとあり、これはそうかもなあと思った。自分は割と情報をかき集めてから手を動かし始めるところがあり、無駄な情報もあるし直接関係ない情報を参考にしてしまったりもする。知りすぎると自分の意見も失われてしまう。
この記事を書くときに、他の人はどういうまとめ書いてるのかなとググったら、その記事に引っ張られて自分の記事もその記事と似たようなことを書こうとしてしまったので、まさにこれなのかなあと思ってそれらのページは閉じて自分で考えて文字を書いている。

読み終わって、ちゃんと本質を見極めるのが大事だよねというのを再認識した感じでまあよかった。

ただ、Audible で聴いた時に全然頭に入らなかったのが、 Kindle で読んだ時にも頭に入ってこないことが多くて、それは具体例が多すぎるのが原因かなあとも感じた。その具体例も毎回の章ごとに違う具体例が出てきていて、いまなんの話だっけ... ってなってしまったので、同じ具体例をストーリーを仕立てにして出してくれたらもっと理解しやすかったかも、と思った。