ntaoo blog

主にDart, Flutter, AngularDartについて書いていきます。ときどき長文。日本語のみ。Twitter: @ntaoo

Google I/O 19 Dart関連セッションの視聴メモ その2: Pragmatic State Management in Flutter (Google I/O'19)

Pragmatic State Management in Flutter (Google I/O'19)

Flutterで状態管理をする方法についてのセッション。去年も同じペアで同じテーマのセッションがあった。

https://www.youtube.com/watch?v=d_m5csmrf7I&list=PLjxrf2q8roU2no7yROrcQSVtwbYyxAGZV&index=4&t=0s

ひとつのWidget Treeしかないなどの単純なアプリの場合をのぞき、アプリの状態管理は重要な問題となる。

Agenda

  • 状態管理の重要性
  • さまざまな取り組みの推移
  • Flutterでの実践的な状態管理方法

論点

  • 理解しやすく読みやすく保守しやすいこと
  • テストしやすいこと
  • 実行性能(パフォーマンス)が高いこと

残念ながら万能の解決策はないので、あなたのユースケースのなかでエッジケースを考慮するべき。

案1: グローバル変数で状態共有

動くが、ユニットテストも保守も難しい。Widgetが強結合してしまっている。本当に単純なアプリ以外ではするべきではない。

UI = f(state)

  • UIの状態は、アプリケーションの状態を反映したもの
  • UIは他のUIの状態を直接管理しない。
  • なんらかのNotifierで状態をUIに通知する

案2: package:scoped_model

ScopedModel WidgetWidget Treeの先祖(ancestor)に配置し、その子孫(descendant)達に通知する。(現在は、後述のpackage:providerで代替できる。)

案3: BLoC

複雑なアプリ向け。Streamを活用。かなり複雑なアプローチ。

案4: package:provider

package:provideでなくpackage:provider。

package:provideは、ScopedModel version 2とでもいうべきもので、Googleからオープンソース化されたが、同時期にCommunityにより開発されたpackage:providerのほうが優れた選択肢という結論になり、package:providerが公式に推奨された。package:provideは非推奨に。

複雑なアプリを書くためには、

  • Dispose callbackでリソースの後始末をする
  • ChangeNotifierProvider、StreamProviderなどの機能を活用してレンダリングコストを抑える
  • MultiProviderで複数の依存を管理する

Single State Objectを避ける

ただ一つの巨大なState Objectは作りたくない。通常は、精緻な状態管理のためには種別ごとにComponent, Classを分ける。 Providerによってそうすることができる。 (筆者注: これはReduxの一枚岩stateを批判をする文脈ではなく、package:providerで状態を分割管理して更新する文脈)

Performance, Measure

  • Widget Treeの更新範囲をできるだけ小さくする
  • Flutter Driverでパフォーマンス測定してコードの最適化のための根拠にする。(測定なしに当てずっぽうでコードの最適化をしない)

https://medium.com/flutter-io/performance-testing-of-flutter-apps-df7669bb7df7

package:providerが万能、というわけではない

以下のような場合は単純にStateful WidgetsetState()で状態管理すれば十分で、package:providerは過剰設計。

  • とてもシンプルなアプリの場合の状態管理
  • Animation Widgetのような、ひとつのWidgetカプセル化された内部のWidget Treeの状態管理

Testing

Flutterにはheadless test frameworkが同梱されている。ひとつのテストを数ミリ秒で実行できる。

まとめ

  • 状態管理においてScoped Modelはそんなに精巧な手法ではないが、申し分なく良い手法。(筆者注: ここでいうScoped Modelは、Scoped Model version 2であるpackage:provide、そしてそれを非推奨にして代わりに推奨しているpackage:providerを指すと解釈した)

  • 詳細な状態管理が必要になりStreamとRxDartを好む場合は、BLoCパターンが素晴らしい手法。

  • Reduxのパターンに慣れている人には、Flutter向けReduxライブラリが申し分ない手法。しかし、Flutterをこれから始めるならば、package:providerで状態管理を始めることが本当に信頼できる良い選択肢である。

所感

  • 詳細な状態の制御が必要ならばBLoCが最適
  • FlutterでのReduxの採用は、Reduxに慣れていてこだわりがある人以外にはあまり推奨しない
  • いまからFlutterを始めるならば、Providerで状態管理を始めてみてBLoCパターンにも挑戦すると良い

結局はProvider + BLoCに落ち着きそう。最初からProvider + BLoCの決め打ちで状態管理をするのも十分アリだと思う。BLoCの難点は初学者にとってはStreamの学習コストが重いことだと思う。

ProviderはAngularでいう階層型Injectorと解釈できる。昔はDagger2ベースのユニバーサルDIパッケージの開発の構想があったが、実現はせずに代わりにFlutterではProviderを、AngularではAngularのDIの仕組みを使い続けることになるようだ。

UIとModelの通信には、ProviderかBLoCを状況に応じて選択という結論。Model自体の内部の設計についてはこのセッションではなにも語られていないが、主にJavaで培われてよく知られているオブジェクト指向デザインパターンおよびクリーンアーキテクチャ的な設計パターンを適用していけばよく、BLoCパターンについてもデザインパターンへの理解があるならばあとはStreamの操作方法を理解すれば習得できる。

FlutterとMVC

ここで述べられているUI=f(state)は、結局、MVCアーキテクチャを関数スタイルで言い換えたもの。Controllerを通じて状態変更のためのメッセージをModelに送り、状態変更したModelの状態をViewに反映する。PDSしてModel Driven View。WidgetView + Controller