FlutterのBLoCパターンについて得た知見
本稿は、FlutterのBLoCパターンについて調べていく中で得られた知見をまとめたものです。
記事メタ情報
筆者の背景・執筆の動機・対象読者について
筆者の背景
普段はwebの開発に当たることが多く、大規模アプリの開発よりは小規模アプリや準静的ページの開発が多いです。
Androidアプリは高校1年生のころにJava(当時はKotlinなどという言語は存在しなかった……)でほとんど使い物にならない書籍管理アプリを作った程度の経験値です。iOSアプリの開発は未だに未経験です。
今回、webの案件が一段落したので、Flutterを使ってネイティブアプリに手を出してみることにしました。Widgetの使い方やTransition、Animationの使い方は何となく分かったのですが、アプリ内でのデータの受け渡しがよく分かりませんでした。Flutter経験者の友人に相談したところ、「BLoCパターンというアーキテクチャをGoogleは推しているっぽいよ」とのことだったので、集中して調べてみることにしました。
執筆の動機
「BLoCパターンとは何か」という情報は英語記事を中心に山ほど出てくるのですが、日本語での記事や実際にどう実装すれば良いのかといった記事が非常に少なかったです。
同じことを調べる人の手間を省くためにも、自分的に参考になった記事へのリンクを交えつつ知見をまとめていきたいと思います。
対象読者
BLoCパターンについて調べている、主に初心者が対象読者です。
経験者が満足できるような情報を提供できる自信はありません……。間違っていたり改善できるような知見が書かれていたらぜひTwitter @dev_sudame までリプ or DMお願いします!
目次
前提知識編
Flutterの概要
Flutterとは、Googleが開発しているモバイルネイティブアプリのフレームワークです。Dart言語を用いて記述し、簡単なアプリであればFlutterで書くだけでAndroid/iOSの両方に対応できます。
Flutterの公式ページには次のような説明がされています。
Flutter allows you to build beautiful native apps on iOS and Android from a single codebase.
Flutterの大きな特徴としては、レイアウトもUIコンポーネントも、UIのほぼすべてを Widget
を階層的に積み重ねて記述する点が挙げられます。
今回Futterに入門される方は、参考リンクにも挙げたFlutterの効率良い学び方を一読することをオススメします(著者のmonoさんの記事はどの記事も非常に参考になるのでありがたい限り……!)。
本稿はFlutter自体の入門解説は想定していないので、詳しい情報は参考リンクに譲ることにします。
参考リンク
- Flutter - Beautiful native apps in record time
- Flutterの公式サイトです。Flutterは公式ドキュメントが(英語ですが)非常に充実しています。基礎的な部分は公式サイトを見ておけば大丈夫だと思います。
- Flutter YouTube Channel
- Flutterの効率良い学び方
- 入門時のロードマップが示されています。初心者向けの記事リンクも充実しているので、Flutterを学び始めたときのハブとして活用させていただきました。
Flutterのデータ/ステート管理
アプリケーション設計で不可欠かつ最も重要なのはステート管理(状態管理)です。アプリケーションが現在どのような状態にあるのかを適切に管理できなれければ、画面への情報の表示も外部との通信もできません。
Flutterには確固たる一つのステート管理法が存在するわけではなく、開発者が各々手に馴染むものを、適材適所で採用するスタイルのようです。公式ドキュメントには以下のステート管理法が紹介されています。
- setState
- InheritedWidget & Scoped model
- Redux
- BLoC / Rx
- MobX
それぞれについて軽く説明していきます。
setState
setStateは最も素朴なステート管理法で、Flutter公式の最初のチュートリアルでも使われています。習得は簡単ですが、大規模アプリケーションの開発には向かなさそうです。
InheritedWidget & Scoped model
InheritedWidget & Scoped modelはFlutterに独特のステート管理法だと思います。
InheritedWidgetはFlutterに組み込まれている基本的なWidgetの一つで、子孫Widgetからデータにアクセスさせたいときなどに用いられます。
Scoped modelはFlutterのプラグインとして提供される機能です。ただ、Flutter公式のステート管理のチュートリアルにも用いられており、準公式的な立ち位置にいます。InheritedWidgetと同様、子孫Widgetからデータに簡単にアクセスさせる時に用いられるようです。
Redux
webアプリケーションの開発の際に用いるフレームワーク「React」で広く用いられるステート管理法の一つです。こちらに関しては記事が充実しているので説明は省略します。
BLoC / Rx
本記事で詳しく触れていく管理法です。公式ドキュメントでは(なぜか)触れられていませんが、Google I/O 2018で紹介されるなど、Googleが強く推していると見られる実装方法です。
MobX
React界隈を中心にReduxと対比されるステート管理フレームワークです。Reduxよりも手数が少なくリアクティブな実装をできるようですが、僕は使ったことがなく、全然調べられていません。
参考リンク
- List of state management approaches - Flutter
- Flutter公式ドキュメントにある、ステート管理法の一覧です。サードパーティの記事も紹介されており、現在もときどき更新されています。
- InheritedWidget/InheritedModelとは何か - Qiita
- InheritedWidgetの日本語記事です。図柄入りで解説されているので読みやすいです。
- Redux入門【ダイジェスト版】10分で理解するReduxの基礎 - Qiita
- kiita312さんが連載していたReduxの解説シリーズのダイジェスト版です。軽く概念を理解するのにオススメです。
- Introduction | MobX
- MobXの公式サイトです。
MobXについては初心者以下なのでよく分かりません……。
- MobXの公式サイトです。
BLoCとは
BLoCは「Business Logic of Component」の略です。ビジネスロジック(Business Logic)は、Wikipediaによると、こう定義されています。
ビジネスロジック(英: business logic)は、データベース上のデータに対する処理手順といったようなものを指す、ソフトウェア工学的な用語である。
Flutterで言うビジネスロジックは、View(Widget)とModelをつなぐ部分にあります。Viewからデータを受け取り、Modelとやりとりをしてステートの更新をし、Viewにステートの変更を通知する役割を持っています。
Google I/O 2018では以下のような画像で紹介されました。
電波塔のようなアイコンの部分でデータの変更を取得し、スピーカーのようなアイコンの部分に変更されたデータを流し込んでいるようなイメージです。具体的な実装方法については後ほど説明するので、ここでは概念的な説明に留めておきます。
参考リンク
- Flutter / AngularDart – Code sharing, better together (DartConf 2018)
- BLoCが初めて紹介された講演です。DartConfで紹介されたんですね。
- Google I/O '18 | Build reactive mobile apps with Flutter
- Google I/OのFlutterセクションでの講演です。この講演の後からFlutterではBLoCパターンを使うという共通認識が界隈内に生まれたようです。
- 長めだけどたぶんわかりやすいBLoCパターンの解説
- 長めだけど分かりやすいです。
Streamの概念について
さて、Dart言語には言語組み込みの概念として Stream
が定義されています。Streamは入り口と出口があり、入り口には sink
、出口には listen
を用意しておきます。
Stream
の役割は入り口と出口を結ぶことです。入り口と出口は別の場所にあって構いません。先に出したビジネスロジックの例で言えば、4本の Stream
が使われています。水色の2本は入口がView(Widget)で出口がbusiness logic、紫(?)色の2本はbusiness logicが入口でView(Widget)が出口に設定されています。
補足 Google I/Oで紹介された画像は、business logicが主語の文脈で紹介されています。このため、business logicに入力されるデータをlistenし、business logicから出力されるデータをsinkに流し込んでいるイメージです。逆にView(Widget)を主語に考えれば、business logicへデータを出力することはsinkに流しこむことに、business logicからデータを受け取ることはデータをlistenすることに対応します。
参考リンク
RxDartについて
RxDartはDartのサードパーティライブラリです。Dartには上述の通り、デフォルトで強力なStreamクラスを持っていますが、RxDartはさらに使いやすい機能を提供してくれます。
Rx
の接頭辞からも分かる通り、RxJavaやRxJSで有名なReactiveXプロジェクトの一つとして整備されているライブラリです。AndroidアプリでRxJavaを使ってリアクティブ開発をしていた方にはRxDartが馴染みやすいかもしれません。
Dart純粋のStreamクラスにはキャッシュ機能が無く、例えば新規にStreamを開通させたときに初期値を設定したりするのが難しいのですが、RxDartを用いればそれが簡単に実現できます。
Rx系ライブラリではSubject
というクラスを使ってデータの受け渡しを行います。Subject.add()
でデータを流し込み、Subject.stream.listen()
でデータのリッスンが可能です。Subject系のクラスには用途によって様々な兄弟クラスが存在します。兄弟クラスの詳細については参考リンクの解説に詳しいので、そちらに譲ることにします。
RxDartはFlutter開発においても非同期ライブラリでは覇権を握っていると言っても過言ではなく、特に何の説明もなく解説記事に使われていたりもするので、軽く頭に入れておくと良いです。
参考リンク
- rxdart | Dart Package
- RxDartの配布ページです。右カラムの中にAPIリファレンスへのリンクがあるので、そちらも参考にすると良いです。
- Rxで知っておくと便利なSubjectたち - Qiita
- Rx系ライブラリに共通なSubject系クラスの解説がされています。DartはStreamの機能が標準で強力なため、はじめのうちはSubject系クラスのみ理解しておけば良さそうです。
- ReactiveX
- Rx系ライブラリの親元の公式サイトです。Observer/Observableなどの概念の解説があるので、「Subjectって結局なんだよ」と思い始めたら読むと良いと思います。
- 残念なことに日本語対応はしていません。
実装編
ディレクトリ構成
BLoCパターンを実装した場合、どのようなディレクトリ構成にするのが良いのかは悩みどころです。一般的には、次のような構成を取っていることが多いようです。(というか、僕はこうしています。)
┣ lib ┃ ┣ blocs ┃ ┣ models ┃ ┣ views ┃ ┗ helpers ┣ android ┗ ios
見ての通りですが、blocs
の中にはBLoCクラスを入れます。models
にはModelにあたるクラスを、views
の中にはViewとなるWidgetを組み合わせたものを入れておきます。helpers
の中には、後述するBLoCProviderなどのヘルパー系クラスを入れてあります。
参考リンク
- felangel/bloc
- ToDoアプリの例です。このリポジトリでは、
views
をscreens
とwidgets
に分離しています。これもよく見るパターンです。アプリ内のページの数が増えてきたら、こちらの方が管理しやすそうです。
- ToDoアプリの例です。このリポジトリでは、
- vaygeth89/reactive_flutter_todo_database_app
BLoCパターンが守るべき条件
BLoCパターンを始めに提唱したのはAngularDartです。
その提唱者によるGithHub issueコメントでは、BLoCを用いる場合には以下の条件を守る必要があるとされています。
- Inputs and outputs are simple Streams/Sinks only
- Dependencies must be injectable and platform agnostic
- No platform branching allowed
- Implementation can be whatever you want if you follow the previous rules
簡単に訳してみると以下のようになります。
- BLoCの入出力はStreamとSinkのみである
- BLoCの依存関係は必ず注入可能であり、プラットフォームに依存してはいけない
- BLoC内でプラットフォームによる条件分岐をしてはいけない
- 以上のことを守りさえすれば、どのように実装しても構わない
「もちろん、すべてのコンポーネントがBLoCを持つ必要はありません。『ある程度複雑な(complex enough)』コンポーネントがBLoCを持つべきです。」とした上で、コンポーネント側(Flutterで言えばWidget側)が満たすべき条件としては、以下が挙げられています。
- 「ある程度複雑(complex enough)」なコンポーネントは、対応するBLoCを持つべきである
- コンポーネントは入力されたものを「そのまま」BLoCに送るべきである
- コンポーネントはBLoCからの出力を可能な限り「そのまま」表示するべきである
- すべての条件分岐はBLoCからのシンプルな真偽値の出力に基づいて行われるべきである
以上の条件を満たすようにアプリケーションの状態管理を設計すれば、自然とBLoCパターンに則ることになります。
参考リンク
- Flutter / AngularDart – Code sharing, better together (DartConf 2018)
- Google I/Oで紹介された、FlutterとAngularでいかにコードを共通化するかの紹介プレゼンです。
- BLoCについての解説が分かりやすいため、一度流し見すると良いと思います。
- Explore "BLoC" · Issue #12 · tenhobi/flashcards
- FlutterのBLoCパターンをAngularで理解する - lacolaco
BLoCクラスの実装
BLoCクラスには最低限以下の要素が必要です。
BLoCでは、入力用のストリームをリッスンし、適切な処理を施して出力用のストリームに流し込みます。
以下に簡単なサンプルコードを示します。ToDoアプリを作ることを考えます。
モデル
ToDoの一つのアイテム(Task)は以下のように定義しておきます。
Taskは title
と isCompleted
と id
を持っています。細かい部分はコメントを読んでください。
class Task { final String title; final bool isCompleted; final int id; Task({ this.title = '', this.isCompleted = false, this.id, }); // 新しいTaskを作るときに呼び出す // Taskのインスタンスを更新するのではなく、新しいインスタンスを立てる Task copyWith({String title, bool isCompleted, int id}) { return new Task( title: title ?? this.title, isCompleted: isCompleted ?? this.isCompleted, id: id ?? this.id, ); } // idが同じであれば同じTaskであるとみなす // ==演算子の上書き @override bool operator ==(other) => this.id == other.id; // 同一オブジェクトであるとみなすためにはhashCodeを同一にする必要がある // ここではhashCodeをTaskのidにすることで解決 @override int get hashCode => this.id; }
モデルに基づいてBLoCを組み立てます。
先に紹介したBLoCの原則の1つ「コンポーネントは入力されたものを『そのまま』BLoCに送るべきである」にやや反しますが、View(Widget)からBLoCに入力情報を送るときは TaskEvent
を作って送ります。
TaskEvent
は action
として create
update
delete
の3つを指定でき、それと task
を合わせて送出することで「どのTaskに何の処理するのか」を表現しています。
先に説明した通り、TaskBlocクラスには入力用と出力用のストリームがそれぞれ用意してあります。また、それらのストリームを制御する用のプライベートフィールドも定義しています。
なお、TaskBlocクラスはBlocクラスを継承していますが、この事情は後ほど説明します。
// dart lang import 'dart:async'; // 3rd party packages import 'package:bloc_provider/bloc_provider.dart'; import 'package:rxdart/subjects.dart'; // self packages import 'package:sudame_todo_bloc/models/task.dart'; // CRUDアクションの設定(Readは無し) enum TaskEventAction { create, update, delete, } // TaskをC(R)UDする指示を出すイベントクラス class TaskEvent<T> { final TaskEventAction action; final Task task; TaskEvent({this.action, this.task}); } // BLoC本体 class TasksBloc implements Bloc { // Taskのリスト static List<Task> _taskList = []; // 入力Streamの制御 final StreamController<TaskEvent> _inputController = StreamController(); // 出力Streamの制御 final BehaviorSubject<List<Task>> _outputController = BehaviorSubject.seeded(_taskList); // 入力Stream Sink<TaskEvent> get setTask => _inputController.sink; // 出力Stream Stream<List<Task>> get getTaskList => _outputController.stream; // Taskを新規で作る void _createTask({Task task}) { final int _id = _taskList.length; _taskList.add(new Task( title: task.title ?? '', isCompleted: task.isCompleted ?? false, id: _id, )); } // Taskの更新 void _update(Task task) { _taskList[task.id] = task; } // Taskの削除 void _delete(Task task) { _taskList.removeWhere((t) => task == t); } // 入力Streamのリスナ void _taskEventListener(TaskEvent e) { if (e.action == TaskEventAction.create) { // createアクションだった場合 _createTask(task: e.task); } else if (e.action == TaskEventAction.update) { // updateアクションだった場合 _update(e.task); } else if (e.action == TaskEventAction.delete) { // deleteアクションだった場合 _delete(e.task); } // Taskのリスト更新後、出力Streamに流し込む _outputController.add(_taskList); } // コンストラクタ TasksBloc() { // 入力Streamをリッスンしてリスナを登録 _inputController.stream.listen(_taskEventListener); } // ステートが破棄された場合、Streamを閉じて破棄する @override void dispose() async { await _inputController.close(); await _outputController.close(); } }
正直な話、まだ実務レベルでFlutterを書いているわけではありませんし、それほど知識のインプットもできていないので、上のコードを信じ込むことはせず、あくまで参考程度にしてください。
全体のソースはこちらにあります。
参考リンク
- state_experiments/cart_bloc.dart at master · filiph/state_experiments
- Flutterの中の人(Filiphさん)のサンプルコードです。2本Subjectを用意しています。
BLoCインスタンスの扱い方
概要
当然ですが、BLoCはBLoCだけでは意味が無く、View(Widget)と相互にやり取りすることで機能が生まれます。
BLoCへ情報を送り込むWidgetとBLoCから情報を受け取るWidgetは一般的に異なります(同一Widgetで済むならばBLoCパターンにする必要は無いですね)。ここで必要なのは、異なるWidgetから同一のBLoCインスタンスにアクセスするということです。それぞれのWidgetが好き勝手にBLoCインスタンスを立ち上げても、ストリームが異なってしまいますので通信はできません。
異なるWidgetから同一のBLoCインスタンスにアクセスするために考えられる方法は2つあります。
この内、Flutterで良く採用されるのは2です。なぜ1が採用されないのか、僕の知識と調べた範囲では分かりませんでした。ご存知の方はぜひ教えていただきたいです。
2の方法では、Widgetの階層構造の上位でBLoCインスタンスを生成・保持し、下位のWidgetからアクセスすることを考えます。setStateなどでBLoCインスタンスを下位Widgetに順に伝播させていっても良いですが、階層構造が大きくなるとコードが煩雑になり、しかも伝播の流れの中にあるWidgetはその都度再ビルドされていまいます。
そんな状況を解決してくれるのが InheritedWidget
です。
InheritedWidget
公式リファレンスには以下のように紹介されています。
Base class for widgets that efficiently propagate information down the tree. (訳) 階層構造の下位に効率的に情報を伝播するwidgetのベースクラスです。
この説明からも、BLoCインスタンスを効率的に階層構造の下位に伝播するのに最適なクラスだという雰囲気が伝わってきます。InheritedWidget
を用いれば、たとえ階層構造が深くなったとしても、下位のWidgetから一発で(O(1)
で)情報にアクセスできます。
具体的な使い方として、公式リファレンスの例を見てみましょう。
class FrogColor extends InheritedWidget { const FrogColor({ Key key, @required this.color, @required Widget child, }) : assert(color != null), assert(child != null), super(key: key, child: child); final Color color; static FrogColor of(BuildContext context) { return context.inheritFromWidgetOfExactType(FrogColor) as FrogColor; } @override bool updateShouldNotify(FrogColor old) => color != old.color; }
重要なのはstaticなof
メソッドです。of
メソッドの中ではcontext.inheritFromWidgetOfExactType()
というメソッドを呼んでいます。inheritFromWidgetOfExactType()
メソッドは、ざっくり言うと「親Widgetたちの中で最も近い、指定された型を持つWidgetを返す」機能を持ったメソッドです。やや難しいですが、ここで言う「親Widget」にはof
メソッドを定義したクラス(ここではForgColor
クラス)も含まれます。従って、FrogColor
クラスの子孫WidgetがFrogColor
クラスのof
メソッドを呼び出した場合、FrogColor
Widgetそのものが返ることになります。
この動作については参考リンクに詳しいので、そちらを参照してください。
先の例を続けるならば、FrogColor
Widgetの子孫Widgetからは以下のようにしてFrogColor
Widgetを呼び出すことができます。ここで context
は BuildContext
型です。例えば StatelessWidget
の build
メソッド内では BuildContext
にアクセスできます。
final FrogColor _frogColor = FrogColor.of(context);
ところで、先の例では of
メソッドは FrogColor
Widgetそのものを返していましたが、of
メソッドはWidgetを返す必要はありません。例えば、FrogColor
クラスは Color
フィールドを持っていますが、これを返してもOKです。以下のような書き方になるはずです。
static Color of(BuildContext context) { return (context.inheritFromWidgetOfExactType(FrogColor) as FrogColor).color; }
BLoCから話が逸れてしまいました。ここまでのInheritedWidget
の動作を踏まえれば、InheritedWidget
を親としてBLoCインスタンスを子孫に効率よく伝播させることができます。簡略的には以下のように書けるでしょう(本来はもうひと手間必要です)。
class BlocProvier extends InheritedWidget { const BlocProvider({ Key key, @required Widget child, }) : assert(child != null), super(key: key, child: child); // BLoCインスタンスの生成 final Bloc bloc = new Bloc(); static Bloc of(BuildContext context) { // BLoCインスタンスの提供 return (context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bloc; } @override bool updateShouldNotify(BlocProvider old) => bloc != old.bloc; }
子孫クラスからは、下記のようにしてBLoCインスタンスにアクセスできます。
final Bloc _bloc = BlocProvider.of(context);
BLoCインスタンスの適切な生成・破棄
InheritedWidget
を用いることで下位Widgetに効率的にBLoCインスタンスを伝播させることには成功しました。しかしまだ問題があります。それは BLoCインスタンスを適切に生成・破棄する ということです。
使われなくなったWidget(dispose
されたWidget)に紐付いているBLoCは、ストリームを適切に閉じてあげる必要があります。ストリームを閉じない場合、Widgetは存在しないのにBLoCは生き続け(当然ストリームも生き続け)、メモリリークやバグが起こる温床となります。使わなくなったものは適切に片付けなければいけません。
Widgetが使われなくなった場合、破棄されるタイミングで State
クラスの dispose
メソッドが呼び出されます。従って、このdispose
メソッドの中でストリームを閉じる処理を呼び出せば良いことになります。
また同様に、Widgetが使われ始めるタイミングでBLoCインスタンスを生成する必要もあります。これは State
クラスの initState
メソッド内で行えば良いです。
実際には動作しませんが、気持ちとしては以下のようなコードになります。
class BlocProviderState extends State { Bloc _bloc; // initState()内でBLoCインスタンスの生成 @override initState() { _bloc = Bloc(); super.initState(); } // dispose()内でBLoCインスタンスの破棄 // Bloc.dispose()メソッドではストリームを閉じる処理などを想定 @override dispose() { _bloc.dispose(); super.dispose(); } // InheritedWidgetを使って下位Widgetに生成したBLoCインスタンスを伝播 // BlocProviderは前項で出した例のものを想定 @override Widget build(BuildContext context) { return BlocProvider( bloc: _bloc, child: MyHomePage(), ); } }
まとめ
ここまでで、BLoCインスタンスを生成し、下位Widgetに伝播し、破棄することが可能になりました。長くなったのでまとめておきます。
State
クラスのinitState
メソッド内でBLoCインスタンスを生成するState
(StatefulWidget
) の子にInheritedWidget
を配置し、子孫たちにBLoCインスタンスを伝播させるState
クラスのdispose
メソッド内でBLoCインスタンスを破棄する
以上を実装すれば、大規模なFutterアプリでもBLoCパターンを用いたステート管理が実現できるはずです。
参考リンク
- FlutterのBLoC(Business Logic Component)のライフサイクルを正確に管理して提供するProviderパッケージの解説
- またmonoさんの記事です。本項の大部分で参考にさせていただきました。
- bloc_provider | Flutter Package
- 上記の記事で解説されているFlutterパッケージです。
- 本項で説明した内容を各個人で実装するのは(勉強目的以外では)車輪の再発明に他なりません。便利なパッケージを活用するのが良いです。
- InheritedWidget class - widgets library - Dart API
InheritedWidget
の公式リファレンスです。他のクラスに比してもやや丁寧に書かれているので、一読すると良いかもしれません。- 併せて、inheritFromWidgetOfExactTypeメソッドのリファレンスを読むこともオススメします。
- InheritedWidget/InheritedModelとは何か - Qiita
InheritedWidget
について詳しく説明してくれています。上述の公式リファレンスを読みつつこの記事を読めばおそらく理解できます。
- FlutterのBuildContextとは何か - Qiita
InheritedWidget
について理解する中で100%ぶち当たるのが「BuildContext
って何だよ」という疑問ですが、それに対する回答記事です。
総括
かなりの大長編となってしまいました。
僕がFlutterに初めて触れてBLoCパターンについて知りたかったことをすべて詰め込むという意気込みで書きなぐりましたが、まだ不足している情報があるかもしれません。「あれも入れたほうが良い」「ここでハマった」等がありましたら追記しますのでお声掛けください。
繰り返しますが、私は完全な初心者なので、本稿の情報には誤りが多分に含まれている可能性があります。気付き次第修正を入れますので、こちらもどうぞお気軽にお声掛けください。
お疲れ様でした。
(おまけ) 全参考リンク集
記事内に登場したすべての参考リンクを集めてみました。リンク集としてご活用ください。
- Flutter - Beautiful native apps in record time
- Flutterの公式サイトです。Flutterは公式ドキュメントが(英語ですが)非常に充実しています。基礎的な部分は公式サイトを見ておけば大丈夫だと思います。
- Flutter YouTube Channel
- Flutterの効率良い学び方
- 入門時のロードマップが示されています。初心者向けの記事リンクも充実しているので、Flutterを学び始めたときのハブとして活用させていただきました。
- List of state management approaches - Flutter
- Flutter公式ドキュメントにある、ステート管理法の一覧です。サードパーティの記事も紹介されており、現在もときどき更新されています。
- InheritedWidget/InheritedModelとは何か - Qiita
- InheritedWidgetの日本語記事です。図柄入りで解説されているので読みやすいです。
- Redux入門【ダイジェスト版】10分で理解するReduxの基礎 - Qiita
- kiita312さんが連載していたReduxの解説シリーズのダイジェスト版です。軽く概念を理解するのにオススメです。
- Introduction | MobX
- MobXの公式サイトです。
MobXについては初心者以下なのでよく分かりません……。
- MobXの公式サイトです。
- Flutter / AngularDart – Code sharing, better together (DartConf 2018)
- BLoCが初めて紹介された講演です。DartConfで紹介されたんですね。
- Google I/O '18 | Build reactive mobile apps with Flutter
- Google I/OのFlutterセクションでの講演です。この講演の後からFlutterではBLoCパターンを使うという共通認識が界隈内に生まれたようです。
- 長めだけどたぶんわかりやすいBLoCパターンの解説
- 長めだけど分かりやすいです。
- Asynchronous Programming: Streams | Dart
- rxdart | Dart Package
- RxDartの配布ページです。右カラムの中にAPIリファレンスへのリンクがあるので、そちらも参考にすると良いです。
- Rxで知っておくと便利なSubjectたち - Qiita
- Rx系ライブラリに共通なSubject系クラスの解説がされています。DartはStreamの機能が標準で強力なため、はじめのうちはSubject系クラスのみ理解しておけば良さそうです。
- ReactiveX
- Rx系ライブラリの親元の公式サイトです。Observer/Observableなどの概念の解説があるので、「Subjectって結局なんだよ」と思い始めたら読むと良いと思います。
- 残念なことに日本語対応はしていません。
- felangel/bloc
- ToDoアプリの例です。このリポジトリでは、
views
をscreens
とwidgets
に分離しています。これもよく見るパターンです。アプリ内のページの数が増えてきたら、こちらの方が管理しやすそうです。
- ToDoアプリの例です。このリポジトリでは、
- vaygeth89/reactive_flutter_todo_database_app
- Flutter / AngularDart – Code sharing, better together (DartConf 2018)
- Google I/Oで紹介された、FlutterとAngularでいかにコードを共通化するかの紹介プレゼンです。
- BLoCについての解説が分かりやすいため、一度流し見すると良いと思います。
- Explore "BLoC" · Issue #12 · tenhobi/flashcards
- FlutterのBLoCパターンをAngularで理解する - lacolaco
- state_experiments/cart_bloc.dart at master · filiph/state_experiments
- Flutterの中の人(Filiphさん)のサンプルコードです。2本Subjectを用意しています。
- FlutterのBLoC(Business Logic Component)のライフサイクルを正確に管理して提供するProviderパッケージの解説
- またmonoさんの記事です。本項の大部分で参考にさせていただきました。
- bloc_provider | Flutter Package
- 上記の記事で解説されているFlutterパッケージです。
- 本項で説明した内容を各個人で実装するのは(勉強目的以外では)車輪の再発明に他なりません。便利なパッケージを活用するのが良いです。
- InheritedWidget class - widgets library - Dart API
InheritedWidget
の公式リファレンスです。他のクラスに比してもやや丁寧に書かれているので、一読すると良いかもしれません。- 併せて、inheritFromWidgetOfExactTypeメソッドのリファレンスを読むこともオススメします。
- InheritedWidget/InheritedModelとは何か - Qiita
InheritedWidget
について詳しく説明してくれています。上述の公式リファレンスを読みつつこの記事を読めばおそらく理解できます。
- FlutterのBuildContextとは何か - Qiita
InheritedWidget
について理解する中で100%ぶち当たるのが「BuildContext
って何だよ」という疑問ですが、それに対する回答記事です。