この記事は@sensuikan1973さんの【BLoC/RxDart入門】 Flutterの公式チュートリアルを書き換えるをもとにしています。
今回上記のサンプルを写経しつつ、自分なりに更新してFlutterにおけるDIを考えてみました。
きっかけ: Widgetツリーを考えずにインスタンス取得したい
以前からFlutter開発でアプリ内で任意の場所でインスタンスを使いまわす設計、つまりDIに悩んでおり、調べてみるとprovider
パッケージやInheritedWidget
での解決方法が主流のようでした。いくつかのQiitaやブログをみながら色々試したのですが、どうにも違和感がありました。
というのも、自分の場合は「データストアのインスタンスにアクセスしたいだけ」という状況だったので、「UI関係ないインスタンスにアクセスしたいだけのときに、BuildContext
使って取得したり、Provider
とかConsumer
とかで囲わないと伝播できないのなんとかならんかなぁ」と感じていました。
Singletonで同じインスタンス取得できるようにするのもやれるけど他にもないかと探していたらget_it
パッケージというのを見つけたので、今回、InheritedWidget
を使っている前述の記事を写経しつつ、そこからget_it
を使った方法にアップデートしてみて、理解を深めようと考えました。
*正確にはget_it
はDIを実現しているのではなく、Service Locatorパターンを実装したライブラリです。が、そこは今回のきっかけに対する解決策としては十分使えると判断しました。Service LocatorとDIの違いはMartin FowlerのInversion of Control Containers and the Dependency Injection patternを読むとわかりやすいです。
https://www.martinfowler.com/articles/injection.html (原文)
https://kakutani.com/trans/fowler/injection.html (日本語訳)
ソース
アプリのUI
繰り返しになりますが、このアプリは@sensuikan1973さんの記事のアプリを写経した後に、変更を加えたものです。
Startup Name Generatorページ
- 初期画面。ランダムに表示されている単語を「お気に入り」にすることができる。
- お気に入りの総数はナビゲーションバーに表示され、お気に入りの追加・削除で更新される。
- ナビゲーションバーのボタンから次の画面、Your Favoriteページへ移動できる。

Your Favoriteページ
- 自分の「お気に入り」をリスト表示してレビューすることができる。
- デフォルトでは、リストの先頭にダミーのサジェスト項目が表示される。
- また、ダミーの広告表示もリストのいち項目として表示される。
- ユーザーはナビゲーションバーのスイッチで、ダミーのサジェスト項目と広告表示を無効にすることができる。
- リストの言葉を気に入らなくなった場合は、各お気に入り項目の削除アイコンボタンからお気に入りを削除することができる。

ざっくりやったこと
main.dart
で初期化
void main() {
_setup(); // 初期化呼び出し。
runApp(new MyApp());
}
// 中略
_setup() {
GetIt.I.registerSingleton<WordBloc>(WordBloc()); //これ
}
Provider.of<WordBloc>(context)
をGetIt.I<WordBloc>()
に変更
変更にあわせてbuild()
で取得したWordBloc
をメソッド引数で持ち回っていた部分は引数削除。メソッド内でインスタンスを取得。
Widget _buildRow(WordPair pair) { // 引数にあったWordBlocを消して
return new StreamBuilder<List<WordItem>>(
stream: GetIt.I<WordBloc>().items, //ここで直接取得
builder: (_, snapshot) {
if (snapshot.data == null || snapshot.data.isEmpty) {
get_it良いじゃないか
所感としてはかなり自分の満足する形でWidgetツリーのことを全く考えないでインスタンスの取得ができました。特に、Context
を必要とせずにインスタンスを生成できる+GetIt.I<Foo>()
がstaticに使えることでbuild()
を待たずしてインスタンスを取得し、そのままinitState()
で諸々の準備にBlocを使えるようになりました。
class _BlocFavoritePageState extends State<BlocFavoritePage> {
final WordBloc wordBloc = GetIt.I<WordBloc>(); // これ!
List<Item> wordItemList;
bool withInfo = true;
StreamSubscription<List<Item>> _subscription;
@override
void initState() {
super.initState();
_setupSubscription();
}
_setupSubscription() {
if (_subscription != null) {
_subscription.cancel();
}
if (withInfo) {
//initState()から呼ばれてもwordBlocを使える
_subscription = wordBloc.itemsWithInfo.listen((items) {
setState(() {
wordItemList = items;
});
});
} else {
_subscription = wordBloc.items.listen((items) {
setState(() {
wordItemList = items;
});
});
}
}
@override
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(...
// 以下略
上記の様に、インスタンスフィールドに直接セットできる他にも、コンストラクタ引数にしておいて、MaterialApp
のroutes
の中でコンストラクタに渡すといった設計も可能になります。コンストラクタインジェクションと言われるパターンですね。こちらはテスタビリティを上げる戦略として使えるやり方です。
get_itのデメリットもある
get_it
はwidgetツリーとは関係ない場所でインスタンスの取得を行えるため、言い換えればWidgetのrebuildについて何も考慮してくれません。provider
のlisten
やInheritedWidget
のupdateShouldNotify()
はインスタンスの取得と配下のWidgetツリーの更新をまとめて制御してくれる点が1つのメリットという理解ですが、get_it
はそんなことはしてくれません。
なので、get_it
を使ってWidgetを更新するためには、WidgetをStatefullWidget
にしてsetState()
でデータを更新していったり、StreamBuilder
を間に挟むなど、どうやってrebuildをさせるか自分で考える必要があります。
一方で、provider
やInheritedWidget
のその辺りの挙動をもう理解している人はget_it
の旨味はあまりないかもしれません。
まとめ
上記の通りデメリットもありますが、私の場合は「やってることがシンプル」「デメリットのrebuildの制御は見える様になるのが嬉しい」ので、provider
、InheritedWidget
、get_it
どれでも実現できるロジックならget_it
をメインで使おうと思います。
なお、念のためですが、provider
とget_it
は目的が違うライブラリなので優劣はないと考えています。(もし目的が完全一致するものであれば比較できますが。)
ツールは優劣ではなく、どの様な設計をしたいかに合わせて適切な道具を選べる、選択権を持っていることが重要です。そういう意味ではFlutterでDIを考えたときにこのような選択肢があることは素晴らしいことですし、自分もget_it
を使いつつ、get_it
偏重にならないようにprovider
も使える様になっていかないとなぁと思う次第です。
おまけ
DaggerにインスパイアされたInject.dartっていうのも見つけましたが今回は未チェックです。
https://github.com/google/inject.dart
2019年前半から1年くらい更新ない。。。どうなるんだろう?