LoginSignup
25
15

More than 3 years have passed since last update.

FlutterでDIならget_itもいいじゃないか

Last updated at Posted at 2020-06-14

この記事は@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(...
// 以下略

上記の様に、インスタンスフィールドに直接セットできる他にも、コンストラクタ引数にしておいて、MaterialApproutesの中でコンストラクタに渡すといった設計も可能になります。コンストラクタインジェクションと言われるパターンですね。こちらはテスタビリティを上げる戦略として使えるやり方です。

get_itのデメリットもある

get_itはwidgetツリーとは関係ない場所でインスタンスの取得を行えるため、言い換えればWidgetのrebuildについて何も考慮してくれません。providerlistenInheritedWidgetupdateShouldNotify()はインスタンスの取得と配下のWidgetツリーの更新をまとめて制御してくれる点が1つのメリットという理解ですが、get_itはそんなことはしてくれません。

なので、get_itを使ってWidgetを更新するためには、WidgetをStatefullWidgetにしてsetState()でデータを更新していったり、StreamBuilderを間に挟むなど、どうやってrebuildをさせるか自分で考える必要があります。

一方で、providerInheritedWidgetのその辺りの挙動をもう理解している人はget_itの旨味はあまりないかもしれません。

まとめ

上記の通りデメリットもありますが、私の場合は「やってることがシンプル」「デメリットのrebuildの制御は見える様になるのが嬉しい」ので、providerInheritedWidgetget_itどれでも実現できるロジックならget_itをメインで使おうと思います。

なお、念のためですが、providerget_itは目的が違うライブラリなので優劣はないと考えています。(もし目的が完全一致するものであれば比較できますが。)
ツールは優劣ではなく、どの様な設計をしたいかに合わせて適切な道具を選べる、選択権を持っていることが重要です。そういう意味ではFlutterでDIを考えたときにこのような選択肢があることは素晴らしいことですし、自分もget_itを使いつつ、get_it偏重にならないようにproviderも使える様になっていかないとなぁと思う次第です。

おまけ

DaggerにインスパイアされたInject.dartっていうのも見つけましたが今回は未チェックです。
https://github.com/google/inject.dart

2019年前半から1年くらい更新ない。。。どうなるんだろう?

25
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
15