Flutter の状態管理についてお絵かきアプリ作りながら勉強した

Flutter の基礎を固めるにあたり、簡単なアプリを作りつつ調べつつやってみた。特に状態管理について勉強した。

## 作ったもの

成果物: https://github.com/macoshita/simple_oekaki

![screenshot.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17722/f95dad4d-7095-df85-2bff-3709c49add1f.jpeg)

CustomPainer を使ってみたかったので、お絵かきアプリを作ってみた。
[すでに作っている方がいて](https://qiita.com/takenobi/items/c2342cd94abf9e020220)、見た目はそれに引きずられている。
コードは一切見ずに作ったので、改めて見てみたら同じようなアプリでも結構違う感じになっている。

[CustomPainter](https://api.flutter.dev/flutter/rendering/CustomPainter-class.html) はなんか HTML Canvas みたいなことができる Widget.
お絵かきみたいな使い方だけじゃなく、吹き出しとかちょっと複雑な図形を書くならこれを使うことになる？

## 構成

ウィジェットツリーは下記のようになっている。

![oekaki_widget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17722/993da663-17e3-21b3-890d-96bf18315c07.png)

PaperScreen がお絵かき画面。
その画面がカラーパレットとかキャンバスペーパーとかクリアボタンを持っている。
ペーパー側はカラーパレットで選択されてる色で線を引かなきゃいけないし、クリアボタンが押されたら全ての線を消さなきゃならない。

## State Management

上記のようにコンポーネントを分けたとき、一番素直な state 管理は PaperScreen が全ての状態を持つことじゃないかと思う。
ペンの色や現在の描画状態を PagerScreen に持たせて、ぶら下がる各 Widget のコンストラクタとかで渡す方法だ。
だがそうすると、ペンの色や現在の描画状態を変えるメソッドも渡す必要がでてくるため、コードが複雑になる。
また Palette コンポーネントあたりはさらに細かいコンポーネントに分けていく可能性が高い（線の太さを変えるコンポーネントを足したりとか）ので、 state をコンストラクタで渡す→コンストラクタで渡す→コンストラクタデワタス→……となりかねない。
（この辺の例示は [公式のシンプルな例](https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple) の方がわかりやすい）

ということで、こんなシンプルなアプリでも State Management は重要になる。
状態管理ライブラリは JS だと Redux, vuex, mobx ... と山のようにライブラリがあり、使ってるフレームワークとの相性の良さで選ぶという感じ。

flutter では、 flutter state management でググると [flutter.dev の記事](https://flutter.dev/docs/development/data-and-backend/state-mgmt) にたどり着き、これが [Pragmatic State Management in Flutter (Google I/O'19)](https://youtu.be/d_m5csmrf7I) で紹介されていた Provider を使っていたので、まるっと参考にした。
（途中に出てくるサンプルコードのコミット時刻的に、最近更新された記事っぽい？）

### Provider を用いた構成

大まかな仕組みは以下の通り。

1. ChangeNotifier を継承したモデルを作成
2. モデルの更新メソッドで notifyListeners を叩いて変更通知
3. ChangeNotifierProvider Widget でモデルの変更通知を受け取る
4. 上記 Widget の child が変更後のモデルを使って build し直される

上記に従い、このアプリでは ChangeNotifier を継承した PenModel と StrokesModel を用意し、下記のようなコードを書いた。

- Paper Widget でグリグリしたら、 StrokesModel に新しいストロークを追加する
    - この際、 PenModel の色を使うようにする
- Palette Widget で適当な色をタップしたら PenModel の色をその色に変更する
- Clear Button をタップしたら、 StrokesModel の全てのストロークをクリアする
- PaperScreen で StrokesModel, PenModel を ChangeNotifierProvider に登録する

図にするとこんな感じ。

![oekaki_widget.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/17722/2c46687e-5af0-9900-39fe-eddf66282554.png)

上記の Widget はすべて StrokesModel, PenModel の状態を使ったり更新したりしているだけで、ほかは何も意識していない（実際 StatelessWidget になっている)のに、期待通りに動いてくれる。
もし新しいモデルを増やしたければ PagerScreen にモデルを登録し、そのモデルを使いたい Widget のコードを変更するだけで済む。

## 所感

2, 3 時間でヌルヌル動いてコードもまぁまぁキレイなお絵かきアプリが作れた。
なおこのコードをそのまんま iPhone 6 に入れてもヌルヌル動いたので、このレベルのアプリなら端末スペックが問題になることはあんまりなさそう。
状態管理は Rx みを感じる。もっと複雑なアプリを作るとわからないけど、とりあえず不満はない。

### Flutter の感想

とにかく開発環境が神ってる。 VSCode にプラグイン入れて書いてるけど、ライブリロードが異常に速いし、ブレークポイントも再起動無しで仕込めるし、デバッグビルドでもそこそこ速い。
困ったら公式ドキュメントと [mono さんの記事](https://medium.com/flutter-jp/) がだいたいなんとかしてくれる。
あと Dart の学習コストはめちゃめちゃ低い。なんでも良いから C 系一つ触ってれば書けるんじゃね？　くらい。
Flutter for Web もまだバグが多いが期待値高い。「 DOM なんてなかった」と言わんばかりの Canvas ゴリゴリ実装はちょっと好みじゃないが、速さは絶対正義なのでしゃーない。

### React Native との比較

全体的な速度が速い。へんなトリックを使わなくても速い。特に起動速度は段違いで速い。
なにかと JSCore が必要な RN 的に、ここが同等になることはなさそう。

ただ RN はコミュニティのデカさと Code Push などの機能、そしてなにより TypeScript, TSX, CSS ライクというのは今後も WEB エンジニアを魅了していくんだろう。