この記事の目的は、既にAngular、もしくはAngularDartでの状態管理手法を理解しており、これからFlutterの状態管理手法を学びたいと言う方に向けて、Angularの知識を梃子にして少ない労力でFlutterの状態管理手法を理解していただくことです。私自身の、AngularまたはAngularDartでの経験に基づいています。
FlutterにはWebサポートがあり、WebブラウザーでFlutterを動かすことができます。Flutter on the Webと呼称されています。ただし、現在はβ版であり、プロダクションに導入するにはいくつかの高い壁があるため、要件をよく吟味する必要があります。Flutter on the Webの進捗状況についてはこれから別記事でまとめていく予定です。さしあたっては以下の記事を紹介しておきます。
Flutter on the Webは、私の予想では、おそらく来年か再来年には安定版がリリースされ、エコシステムが充実して適用範囲が大きく広がるはずです。
Flutterの状態管理手法
Flutterの状態管理には、Webかネイティブかといったプラットフォームに依らない標準的な手法が存在します。それは今後おおきく変化するとは考えづらいため、当分は陳腐化しないでしょう。
概要を知る
正攻法はもちろん、公式サイトでまとめられている情報を読んで理解することです。
たとえ英語が苦手でも、機械翻訳を活用して読む価値があります。
Angularの知識で類推する
Flutterの状態管理手法を、上記のリンク先の記事を読んで正攻法で理解していくと共に、既に学んだ知識から類推してその概要を把握していくと、理解の効率が上がるでしょう。実際、私はAngularでの開発経験が長いので、Angularから類推して学んだ側面が大きいです。
ではここから、Angularの知識の類推で、Flutterの状態管理手法を把握していきましょう。
UIの構造
AngularのComponent TreeはFlutterではWidget Treeに相当します。AngularのComponentがView + Controllerと言えるように、FlutterのWidgetもそのように言えます。両者とも、自身に内包した専用のModelを持つこともありますし、ApplicationのModelと通信して、Modelの状態変化を受信してViewを更新することも共通です。小規模なアプリでは、AngularではComponent自体にApplicationのModelを内包するスマートUIパターンが(小規模以上では非推奨ながらも)見られますし、Flutterでもサンプルコードではそのような手軽なパターンで書かれています。
UIの状態更新処理の仕組み
Angularでは、NgZoneによってMicrotaskの処理をフックすることで状態更新の時期が管理され、Change Detectionにより状態の差分チェックを行うことでUIの状態更新処理が自動的に実行されます。この自動化の仕組みの功罪については、Angularで開発した経験があれば身にしみているでしょう。
Flutterでは、Angularのような水準の自動化の仕組みは採用されていません。setState関数をプログラマーが明示的に呼び出すか、InheritedWidgetを利用することで、それらが対象とするWidgetとそのサブツリーをリビルドさせることを通じてUIの状態更新処理を実行します。Angularよりは黒魔術度が低いため、状態更新処理を操る手法に関して考慮すべき要素は増えていますが、その理解の難易度については、Angularと比較するとやや易しい評価しています。
StatefulWidgetとStatelessWidget
FlutterのWidgetを大別すれば、StatefulWidgetとStatelessWidgetに分かれます。
StatefulWidgetは、自身と対になるStateというObjectを生成して状態を管理します。そして、State ObjectのsetState関数を呼び出すことで、状態の差分チェックを行い、自身とそのサブツリーを構成するWidgetのうち、リビルド対象かどうかを判定して必要に応じてリビルドし、その結果を元に画面更新処理を引き起こします。
State Objectには、ライフサイクルメソッドがあります。AngularのComponentのように、ライフサイクルメソッドをオーバーライドして、任意の処理を追加できます。
StatelessWidgetは、それ自身では状態を管理できないという制約があります。すべてのフィールドはfinalにしなくてはなりません。(finalは、再代入不可を示すキーワードです。JavaScriptの用語ではconstキーワードにあたります。)また、その制約により、それ単体ではリビルドの制御もできません。その制御はStatefulWidgetが担当し、StatelessWidgetはそのサブツリーとして構成されて、StatefulWidgetのリビルド処理の一部として自身をリビルドします。またはInheritedWidgetというものからの状態更新通知を受けてリビルドすることもできます。(このリビルドを回避して画面更新コストを抑えるための手法がいくつかあります)。
StatelessWidgetには、ライフサイクルメソッドがありません。
StatefulWidgetとStatelessWidgetの関係については、Angular ComponentのChangeDetectionStrategy DefaultとOnPushの関係を連想するかもしれません。実際に両者がWidget Treeの中で果たす役割と、両者が使用される割合は似たところもありますので、それはWidgetの理解への最初の一歩としては妥当です。しかし、実際の状態管理の細部は両者の性質は異なり、StatelessWidgetのほうがより厳しい制約があったり、逆にStateless WidgetにはOnPushのComponentにはないような性質もあります。
InheritedWidget
また、Component Treeの離れたComponent間で状態を受け渡すには、 AngularではHierarchical Dependency Injectorsの仕組みに乗って、Serviceを通じて行います。Flutterでは、InheritedWidgetが提供する仕組みで行います。
InheritedWidgetについての詳しい解説については、以下の記事がよくまとまっています。
InheritedWidgetについては、実際にはそれを直接使う代わりに、package:providerというInheritedWidgetを包み込んで使いやすくしたものを使用する手法が標準的です。
ここまで、本来説明したかったことの前提の説明しかしていませんが、時間が押しているので、ここで一旦切って、続編として別記事で公開する予定にします。すいません。
続編のアジェンダです。
- UIの状態更新処理のコストを節約する仕組みを、Angularと比較して解説します。
- Angularでは、そのフレームワーク組み込みのHierarchical Dependency Injectorsを用いて、Dependency InjectionパターンでModelの依存管理をしますが、Flutterは、そのような依存管理はどのように実現するのでしょうか。Flutterには、AngularのHierarchical Dependency Injectorsにあたる仕組みがないため、package:providerまたはpackage:get_itによるService Locator Pattern、および手動のConstructor Injectionを組み合わせることで、Angularと同等の依存管理を実現できます。ClassProvide, FactoryProvider, ValueProviderといった、各種のAngularのDIコンテナへのService登録方法は、Flutterではどのように代替できるのでしょうか。
- Application全体の状態管理手法として、AngularではNgRXフレームワークに依存する手法が人気です。では、Flutterではどのような手法を採用すべきでしょうか。
後編の解説で使用するサンプルアプリのリポジトリーです。AngularDartのチュートリアルで例示されているTour of HeroesアプリをFlutterに移植したものです。