Help us understand the problem. What is going on with this article?

[Flutter] package:provider の各プロバイダの詳細

はじめに

Google I/O 2019 にて provider というパッケージについて言及があり、公に Google 推奨になりました。

使用例を探してみたのですが、scoped_model の代わりに ChangeNotifierProvider を使う例ばかりで、他の多数のプロバイダの情報はほとんど見当たりません。

各プロバイダの使用例について 要望 が上がっているので、そのうち公式に用意される可能性がありますが、まず自分で探り探りサンプルを作りながら調べてみました。
文字どおり「探り探り」ですので、誤りがあった場合にはご容赦ください。

2019/12/25 追記
投稿から一年が経った今では非常に有用なパッケージとしてメジャーになっています。

バージョン

この記事は 2020/5/28 更新時点で provider v4.1.2 に対応しています。

最近の大きな変化:

  • v3.2.0
    • ProxyProviderinitialBuilder/builder がそれぞれ create/update、他の各 Provider の buildercreate に変わり、元の引数名は deprecated となりました。
  • v4.0.0
    • v3.2.0 で deprecated となった引数名が完全廃止されました。
    • Provider.oflisten を指定しなくても使い分けてくれるようになりました。
      • デグレードが起こったため、すぐに v4.0.0-hotfix.1 で無くなりました。
    • StreamProvider.controller() が廃止されました。
    • Selector でコレクションの深い比較が行われるようになりました。
    • SelectorshouldRebuild が追加され、リビルドの条件を指定できるようになりました。
    • 各 Provider によるインスタンス生成と listen 開始が lazy になりました。
      • 必要なタイミングに遅延実行されます。
      • アプリの挙動が過去バージョンと違ってくる場合があるのでご注意ください。
  • v4.1.0
    • BuildContext の三つの extension メソッドが追加されました。
      • context.read()
      • context.watch()
      • context.select()
    • 各 Provider に builder の引数が追加されました。
    • Locator という型定義が追加されました。

活発に開発されていて、破壊的な変更もたびたび加えられています。
今後も大きく変わる場合がありますが、この記事を読んでおけば変更を理解しやすくなるはずです。

provide と provider

provider の他に provide という類似パッケージがあります。
名前までよく似ていて一字違いなのでちょっと紛らわしいですね。

provide はもともと Google 公式で「Scoped Model バージョン2」と言える位置付けだったようですが、数ヶ月前から議論 1 があり、結局 provider のほうが優れているという判断に至ったそうです。2
今後は provider を使いましょう。

何のためのパッケージ?

Google I/O のプレゼンでは Scoped Model の新しい書き方という印象でした。
しかし、作者自身による様々な説明を見ると、それに限定した用途ではないことがわかります。

  • InheritedWidget を使いやすくしてミスを防ぐためのシンタックスシュガー 3 4
  • DI の仕組みを提供 5 6
  • インスタンスの生成と破棄を助ける(StatelessWidget でも dispose() が可能になる等) 7
  • その他何でも(Scoped Model、BLoC 等による状態管理、ValueNotifier 等による Widget 更新など) 8

下位 Widget で値を得る方法

InheritedWidget を使うと、ツリーの下位にある Widget に情報を効率良く渡すことができます。

provider は先述のとおり InheritedWidget のシンタックスシュガーなので使い方が似ていて、プロバイダで設定した値を下位 Widget で得ることができます。

その方法をまず見ておきましょう。複数あります。

Provider.of()

class FooWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<String>.value(
      value: 'この値を下位Widgetで使いたい',
      child: _BarWidget(),
    );
  }
}

class _BarWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final value = Provider.of<String>(context);  // value == 'この値を下位Widgetで使いたい'
    return Text(value);
  }
}

このメソッドには listen という引数もあります。

  • true
    こちらがデフォルトです。
    inheritFromWidgetOfExactType が使われ、変更が notify されるとリビルドが起こります。

  • false
    ancestorInheritedElementForWidgetOfExactType が使われ、リビルドを防げます。
    値の変更に応じた処理(表示更新など)が起こらなくなりますが、そういった処理が不要な箇所(受け取ったモデルのメソッドを使うだけ等)では false にすることで無駄を避けられます。

provider 4.0.0 では自動的に判断して使い分けてくれるようになりました。
次の場合を除き、基本的に明示的な指定は不要です。

  • State.initState() 内で使うとき
  • Provider の create で使うとき

listen の自動使い分けにはバグが見つかり、4.0.0-hotfix.1 で廃止されました。

v4.1.0 では Provider.of() の代わりに使える extension メソッドが追加されましたので後述します。

Consumer()

Provider.of() ではプロバイダの子孫にあたる Widget の BuildContext が必要ですが、Consumer() はそれが得られない場合にも使えて便利です。

class FooWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<String>.value(
      value: 'この値を下位Widgetで使いたい',
      child: Consumer<String>(
        builder: (context, value, child) {  // value == 'この値を下位Widgetで使いたい'
          return Text(value);
        },
      ),
    );
  }
}

また FAQ にあるとおり、次のように使うと Foo や Baz はリビルドせずに Bar だけをリビルドさせることができます。

Foo(
  child: Consumer<A>(
    builder: (_, a, child) {
      return Bar(a: a, child: child);
    },
    child: Baz(),
  ),
)

Baz はあらかじめビルドされてから builderchild に渡されます。
これは AnimatedBuilder() 等の child と同じ仕組みです。

例えば下記コードでは、四角形の高さが変わっていっても Text('ほげ') はリビルドされません。
そうやってパフォーマンスを改善するのに使えるものだと理解しておきましょう。

Consumer<SampleModel>(
  builder: (context, model, child) {
    return Container(
      width: 250.0,
      height: 250.0 + model.value,
      color: Colors.red,
      child: child,
    );
  },
  child: Text('ほげ'),
)

Consumer() の他に Consumer2() から Consumer6() まであり、得たい値の種類数によって使い分けることができます。
個人的にそういう仕様はイマイチに思えましたが、Dart の型システムの制限によるものだそうです。9

provider v3.1.0 で MultiProvider() と組み合わせることもできるようになりました。

Selector()

Consumer() をより便利にしたものです。
provider v3.1.0 で追加され、v4.0.0 で更に機能が増しました。

class DualCounter with ChangeNotifier {
  int count1 = 0;
  int count2 = 0;

  DualCounter() {
    Timer.periodic(Duration(seconds: 1), (t) {
      t.tick.isOdd ? count1++ : count2++;
      notifyListeners();
    });
  }

  ...
}

count1 と count2 の値を1秒ごとに交互にインクリメントして変更を通知するクラスです。
その値を Consumer() で受け取ると、count1 の変更時に count2 用の Text までリビルドされてしまいます。

そこで役立つのが Selector() です。
selector で指定した値が変更されたときだけ builder が呼ばれるように制限できます。
下記では、count1 の値が変わると a の builder、count2 の値が変わると b の builder が呼ばれます。

ChangeNotifierProvider<DualCounter>(
  create: (context) => DualCounter(),
  child: Column(
    children: <Widget>[
      Selector<DualCounter, int>(
        selector: (context, model) => model.count1,
        builder: (context, count, child) => Text(count.toString()),  // a
      ),
      Selector<DualCounter, int>(
        selector: (context, model) => model.count2,
        builder: (context, count, child) => Text(count.toString()),  // b
      ),
    ],
  ),
)

実際に試してみると、Text が交互にリビルドされました。
このように変更に応じる対象を絞れば、余計なリビルドを防いで無駄をなくせます。
flutter_provider_selector.gif

provider v4.0.0 では selector で指定するものがコレクションの場合に深い比較が行われるようになりました。
更に、リビルドの条件を shouldRebuild という引数で上書きできるようになりました。

その例として、GitHub に置いているサンプルに 偶数のみ表示するカウンターの例 を追加しました。

Selector<CnCounter, int>(
  selector: (context, counter) => counter.number,
  shouldRebuild: (prev, next) => next.isEven,  // 偶数になるときのみ
  builder: (context, number, child) => Text(number.toString()),
),

Consumer と同様に他に5つあります(Selector2()Selector6())。
基底クラスである Selector0() も存在しますが、基本的には自分で使う必要はないと思います。

v4.1.0 では Selector の代わりに使える extension メソッドが追加されましたので後述します。

ProxyProvider()

v3.0.0 で追加されました。10
Consumer() に似ていて、他のプロバイダの値を得て利用することができます。

MultiProvider(
  providers: [
    Provider<Foo>(
      create: (context) => Foo(),
    ),
    ProxyProvider<Foo, Bar>(
      create: (context) => Bar(),
      update: (context, foo, prevBar) => prevBar..foo = foo,
    ),
  ],
  child: ...,
)

同類として ChangeNotifierProxyProvider()ListenableProxyProvider() も v3.0.0 で追加されています。
これらも全て Consumer と同様に6つずつあります。

上の例では、別の Provider で渡された Foo が変化したときに Bar をアップデートするものですが、State 等で管理されている値の変化に伴わせるには ProxyProvider0 が使えます。

class _FooState extends State<Foo> {
  int _value = 0;

  void updateValue(int value) {
    setState(() => _value = value);
  }

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProxyProvider0<Bar>(
      create: (context) => Bar(_value),
      update: (context, bar) => bar..value = _value,
      child: ...,
    );
  }
}

context.read() / watch()

v4.1.0 で追加されました。
BuildContext の extension であり、Provider.of() に相当します。

ただし、使用箇所のチェックが Provider.of() よりも厳しいので、同じものと思って使うとエラーが出ることがあります。11

相当するもの 制約
context.read() Provider.of(context, listen: false) ビルド中は不可
各 Provider の create の関数内では使用可
context.watch() Provider.of(context, listen: true) ビルド中のみ可
各 Provider の update の関数内でも使用可

「ビルド中」というのは StatelessWidget や State の build() メソッドが始まってから終わるまでの間のことです。
例外として、read() は各 Provider の create の関数内では使用できます。
また、watch()update の関数内で使用することもできます。

Widget build() {
  final foo1 = context.read<Foo>();   // ダメ
  final foo2 = context.watch<Foo>();  // OK
  final fooValue = context.select((Foo foo) => foo.value);  // OK(後述します)

  return Provider<Foo>(
    create: (context) => Foo(),
    child: ProxyProvider0<Bar>(
      create: (context) {
        return Bar(foo: context.read<Foo>()),     // OK
        //return Bar(foo: context.watch<Foo>()),  // ダメ
      },
      update: (context, bar) {
        //return bar..foo = context.read<Foo>();  // ダメ
        return bar..foo = context.watch<Foo>();   // OK
      },
      child: ...,
    ),
  );
}

さらに、ボタンの onPressed 等のハンドラで使うときにも注意が必要です。

read()の場合
Column(
  children: <Widget>[
    RaisedButton(
      child: ...,
      onPressed: () => context.read<CnCounter>().increment(),  // OK
    ),
    RaisedButton(
      child: ...,
      onPressed: context.read<CnCounter>().increment,  // ダメ
    ),
  ],
)
watch()の場合
Column(
  children: <Widget>[
    RaisedButton(
      child: ...,
      onPressed: () => context.watch<CnCounter>().increment(),  // ダメ
    ),
    RaisedButton(
      child: ...,
      onPressed: context.watch<CnCounter>().increment,  // OK
    ),
  ],
)

read() をビルド中に使えないのは、(確信はないですが)値の変化に応じたリビルドが必要な箇所で誤って read() を使ってしまうミスを防ぐためだと思われます。
ビルド中に使いたければ、これまで通り Provider.of(context, listen: false) を使いましょう。12

こちらの Issue に書かれている作者の説明によると、read() だけでなく Provider.of(context, listen: false) もエッジケース以外では避けたほうが良いそうです。

  • 状態が変わったら表示に反映するのが適当なので watch()select() が良い
  • それ以外の用途は主にタップ等によるメソッドのコールであって、そこ(onPressed 等)では read() が使える

こう考えると、ビルド中に使えないのはあまり困ることでもないですね。

ただし、ケースによっては無駄なリビルドが生じることもあります。
パフォーマンスを確認しながら判断するのが良いでしょう。13

context.select()

v4.1.0 で追加されました。
BuildContext の extension であり、Selector に近い機能を簡潔な書き方で使えるものです。
使いどころは Selector の説明 を参照ください。

Selector<Foo, int>(
  selector: (context, foo) => foo.value,
  builder: (context, value, child) => Text(value.toString()),
)

これを select() を使って書くと次のようにシンプルに書けます。

final value = context.select((Foo foo) => foo.value);
Text(value.toString());

実装は R select<T, R>(R selector(T value)) となっているので、context.select<Foo, int>((foo) => foo.value) のように書くこともできます。

builder

v4.1.0 で各 Provider に追加された引数です。
ややこしいですが、v4.0.0 より前に存在していた builder(現 create)とは異なるものです。

では例を見ましょう。

ChangeNotifierProvider<Foo>(
  create: (context) => Foo(),
  child: Selector<Foo, int>(
    selector: (context, foo) => foo.value,
    builder: (context, value, baz) {
      return Bar(value, child: baz);
    }
    child: Baz(),
  ),
)

これまで、Provider の子ですぐにその Provider で渡そうとした値を使うことはできず、BuilderConsumerSelector といった Widget を挟む必要がありました。
新しい builder の引数は、そうしなくても済むようにしてくれるものです。14

ChangeNotifierProvider<Foo>(
  create: (context) => Foo(),
  builder: (context, baz) {
    final value = context.select((Foo foo) => foo.value));
    return Bar(value, child: baz);
  },
  child: Baz(),
)

サンプルと解説

よくあるカウンターのサンプルです。
右下のボタンをタップすると画面中央の値がインクリメントされます。

flutter_provider_examples.gif

大半のプロバイダでは同じ動作のカウンターにしました。
一部(v3.0.0 で追加されたプロバイダ等)は同じ動作にするのが適していないため、異なるものにしています。

Provider()

ドキュメント によれば、BLoC パターンなどでわざわざ StatefulWidget を使うのを避けるのに使えます。15

そこで、このサンプルでも BLoC パターンを使ってみました。
BLoC パターンの学習までまだ進んでいない方にはわかりにくいかもしれませんが、

  • (a) BLoC をインスタンス化するタイミングに迷う
  • (b) BLoC が不要になったときに StatelessWidget だと自動で破棄されない
  • (c) BLoC パターンを実現しやすくするプロバイダのパッケージが乱立していた

というあたりの問題をそれぞれ次のように解決してくれています。

  • (a) Provider()create にインスタンス生成処理を渡す
    • Provider の State 生成時(Widget ツリーに追加されるとき)に一度だけ実行してくれる
      • 指定例: create: (context) => CounterBloc()
  • (b) Provider()dispose に破棄用メソッドを指定する
    • Provider が Widget ツリーから外されるタイミングで実行してくれる
      • 指定例: dispose: (context, bloc) => bloc.dispose()
  • (c) provider パッケージが無難な選択肢になった 16

BLoC パターンでなくても、インスタンスの生成と破棄を自動的にやってもらいつつ下位の Widget で値を利用したいケースでは役立つはずです。

blocs/counter_bloc.dart
class CounterBloc {
  final _valueController = StreamController<int>();
  Stream<int> get value => _valueController.stream;

  int _number = 0;

  void increment() {
    _number++;
    _valueController.sink.add(_number);
  }

  void dispose() {
    _valueController.close();
  }
}
pages/provider.dart
class ProviderPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<CounterBloc>(
      create: (_) => CounterBloc(),
      dispose: (_, bloc) => bloc.dispose(),
      child: Scaffold(
        body: _CounterText(),
        floatingActionButton: _floatingButton(),
      ),
    );
  }

  Widget _floatingButton() {
    return Consumer<CounterBloc>(
      builder: (_, bloc, child) {
        return FloatingActionButton(
          onPressed: bloc.increment,
          child: child,
        );
      },
      child: const Icon(Icons.add),
    );
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = Provider.of<CounterBloc>(context, listen: false);

    return StreamBuilder<int>(
      stream: bloc.value,
      initialData: 0,
      builder: (_, snapshot) {
        return Center(
          child: Text(snapshot.data.toString()),
        );
      },
    );
  }
}

BLoC パターンについては、以前に関連記事を書いています。
一読されると理解しやすくなるかもしれません。

注意

  • create の処理は一度しか呼ばれません。
    • 値が変化するたびにプロバイダにセットし直したいケースでは .value という名前の付いたコンストラクタのほうを使いましょう。
  • 既に存在するインスタンスをデフォルトコンストラクタで扱うのは安全ではありません。
    • まだ使用が終わっていないうちに dispose される危険性があります。
    • 代わりに Provider.value() のほうを使いましょう。

Provider.value()

Provider() の名前付きコンストラクタ版です。
先ほどと同じ CounterBloc を使ったサンプルにしてみました。

名前付きのほうは BLoC の dispose() が自動的には呼ばれません。
そこで代わりに State.dispose() を使って呼べるように StatefulWidget にしています。

このように Provider() を使った場合より少し手間が多くなります。
Provider.value() は先ほど 下位 Widget で値を得る方法 でご紹介したような単純な値の伝播に向いているようです。

pages/provider_value.dart
class ProviderValuePage extends StatefulWidget {
  @override
  _ProviderValueState createState() => _ProviderValueState();
}

class _ProviderValueState extends State<ProviderValuePage> {
  final _bloc = CounterBloc();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Provider<CounterBloc>.value(
        value: _bloc,
        child: _CounterText(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: bloc.increment,
        child: const Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    _bloc.dispose();
    super.dispose();
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = Provider.of<CounterBloc>(context, listen: false);

    return StreamBuilder<int>(
      stream: bloc.value,
      initialData: 0,
      builder: (_, snapshot) {
        return Center(
          child: Text(snapshot.data.toString()),
        );
      },
    );
  }
}

このサンプルでは使いませんでしたが、次の引数もあります。

  • updateShouldNotify
    • 値に変更がなくても不要なリビルドが起こってしまうのを防ぐものです。 必要に応じて指定しましょう。

StreamProvider.controller()

provider v4.0.0 で廃止されました。
折り畳んで残しておきます。

クリックで開閉

StreamProvider() からご紹介すべきかもしれませんが、v2 系で StreamProvider() だったものが v3.0.0 で StreamProvider.controller() に変わったため、先にこちらを説明します。

Provider()Stream 版のようなものです。
これもインスタンスの生成と破棄をうまく扱ってくれます。

  • create には StreamController を渡す
  • Widget ツリーから外されるときに StreamController.close() が自動的に呼ばれる

注意が必要なのは次の点です。

  • StreamController<int>()create に渡すからと言って StreamProvider<StreamController<int>>.controller とするわけではない(StreamProvider<int>.controller にする)
  • ツリーの下位にある Widget で値を得るときにも Provider.of<int>(context) とする

つまり、下位 Widget で得られるのは単なる int 型の値だということです。


しかし、使い方はわかってもあまりメリットが見えません。
次の二つくらいでしょうか…。:thinking: 17

  • StreamBuilder を使わないで済む
  • BLoC パターン等以外で Sink / Stream によって変更を通知するのに使える

また、StreamController のインスタンスを create で生成せずに、先に生成して変数に入れなければならなかったのも少し腑に落ちません。
create で生成したインスタンスを取得する方法がないと .sink.add() できないので、やむを得ずそうしました。

pages/stream_provider_controller.dart
class StreamProviderCtrlPage extends StatefulWidget {
  @override
  _StreamProviderCtrlState createState() => _StreamProviderCtrlState();
}

class _StreamProviderCtrlState extends State<StreamProviderCtrlPage> {
  int _number = 0;

  @override
  Widget build(BuildContext context) {
    final streamController = StreamController<int>();

    return Scaffold(
      body: StreamProvider<int>.controller(
        create: (_) => streamController,
        initialData: 0,
        child: _CounterText(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => streamController.sink.add(++_number),
        child: const Icon(Icons.add),
      ),
    );
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final number = Provider.of<int>(context);

    return Center(
      child: Text(number.toString()),
    );
  }
}
  • initialData
    • Stream にまだ値が来ていない間の初期値(省略時は null)。

このサンプルでは使いませんでしたが、次の引数もあります。

  • catchError
    • エラーが来たときのフォールバック用の値をここで返します。
      下位 Widget ではエラー時にそのフォールバック値が得られます。
      エラーデータが扱われる Stream であれば必須です。
  • updateShouldNotify
    • Provider.value()updateShouldNotify と同様です。

StreamProvider()

create で生成する対象が、v3.0.0 で StreamController から Stream に変わりました。
代わりに先ほどの StreamProvider.controller()StreamController 版として追加されています。
StreamProvider.controller() は provider v4.0.0 で廃止されました。

他のプロバイダではデフォルトコンストラクタに破棄の機能が備わっていますが、StreamProvider()ドキュメント にはそのことが書かれていません。
自分で StreamController.close() する必要があると思われます。18

pages/stream_provider.dart
class StreamProviderPage extends StatefulWidget {
  @override
  _StreamProviderState createState() => _StreamProviderState();
}

class _StreamProviderState extends State<StreamProviderPage> {
  final _streamController = StreamController<int>();
  int _number = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamProvider<int>(
        create: (_) => _streamController.stream,
        initialData: 0,
        child: _CounterText(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _streamController.sink.add(++_number),
        child: const Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    _streamController.close();
    super.dispose();
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final number = Provider.of<int>(context);

    return Center(
      child: Text(number.toString()),
    );
  }
}
  • initialData
    • Stream にまだ値が来ていない間の初期値(省略時は null)。

このサンプルでは使いませんでしたが、次の引数もあります。

  • catchError
    • エラーが来たときのフォールバック用の値をここで返します。
      下位 Widget ではエラー時にそのフォールバック値が得られます。
      エラーデータが扱われる Stream であれば必須です。
  • updateShouldNotify
    • Provider.value()updateShouldNotify と同様です。

StreamProvider.value()

StreamProvider() とほとんど同じで、異なるのは次の点のみです。

  • create はなく valueStream を指定する

StreamController.close() は自分で行う必要があります。

pages/stream_provider_value.dart
class StreamProviderValuePage extends StatefulWidget {
  @override
  _StreamProviderValueState createState() => _StreamProviderValueState();
}

class _StreamProviderValueState extends State<StreamProviderValuePage> {
  final _streamController = StreamController<int>();
  int _number = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamProvider<int>.value(
        value: _streamController.stream,
        initialData: 0,
        child: _CounterText(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _streamController.sink.add(++_number),
        child: const Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    _streamController.close();
    super.dispose();
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final number = Provider.of<int>(context);

    return Center(
      child: Text(number.toString()),
    );
  }
}

引数は、v2 系で stream だったのが v3.0.0 で value に変わりました。ご注意ください。
value 以外の引数は StreamProvider() と同じです。

ChangeNotifierProvider()

scoped_model パッケージに似ていて、ほとんど同じ感覚で使えそうです。
モデルの状態を変更したときに notifyListeners() で通知するのも同じです。
次のようにして置き換えることができます。

モデルのインスタンスの生成と破棄はよしなに取り計らってくれます。
破棄のほうは Provider() と違って自分で dispose の引数がありません。
ChangeNotifierProvider が Widget ツリーから外されたときに自動的に破棄されます。

models/change_notifier_counter.dart
class CnCounter with ChangeNotifier {
  int _number = 0;
  int get number => _number;

  void increment() {
    _number++;
    notifyListeners();
  }
}
pages/change_notifier_provider.dart
class CnProviderPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<CnCounter>(
      create: (_) => CnCounter(),
      child: Scaffold(
        body: _CounterText(),
        floatingActionButton: _FloatingButton(),
      ),
    );
  }
}

class _FloatingButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<CnCounter>(context, listen: false);

    return FloatingActionButton(
      onPressed: counter.increment,
      child: const Icon(Icons.add),
    );
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<CnCounter>(context);

    return Center(
      child: Text(counter.number.toString()),
    );
  }
}

このサンプルでは ChangeNotifier を使いましたが、その派生クラス(ValueNotifierScrollController など)にも使えます。

ChangeNotifierProvider.value()

ChangeNotifierProvider() と異なるのは次の一点のみです。

  • モデルのインスタンスの生成・破棄は自己責任
change_notifier_provider_value.dart
class CnProviderValuePage extends StatefulWidget {
  @override
  _CnProviderValueState createState() => _CnProviderValueState();
}

class _CnProviderValueState extends State<CnProviderValuePage> {
  final _counter = CnCounter();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ChangeNotifierProvider<CnCounter>.value(
        value: _counter,
        child: _CounterText(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _counter.increment,
        child: const Icon(Icons.add),
      ),
    );
  }

  @override
  void dispose() {
    _counter.dispose();
    super.dispose();
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<CnCounter>(context);

    return Center(
      child: Text(counter.number.toString()),
    );
  }
}

引数は、v2 系で notifier だったのが v3.0.0 で value に変わりました。ご注意ください。

ValueListenableProvider()

ValueListenable というと、そのインタフェースを実装した ValueNotifierValueListenableBuilder と組み合わせて値の変更を Widget に随時反映するのがよくある使い方です。
次の動画を観るほうがイメージしやすいと思います(2分弱の短い動画です)。

ValueListenableProvider() を使うと、ValueListenableBuilder を使わずに同じことが簡単にできます。
動画では InheritedWidget も組み合わせることが提案されていますが、provider パッケージは InheritedWidget のシンタックスシュガーなのでそれも不要です。

なお、Provider.of() で得られるのは create で生成されたインスタンスではない点に注意が必要です。
ValueNotifier.value(本サンプルでは VnCounter.value)の値です。
その値を変更しても Notify されません(そもそも変更もできません)。

create で生成したものは ChangeNotifierProvider() と同様に自動的に破棄してくれます。

models/value_notifier_counter.dart
class VnCounter extends ValueNotifier<int> {
  VnCounter() : super(0);

  void increment() => value++;
}
value_listenable_provider.dart
class VlProviderPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = VnCounter();

    return Scaffold(
      body: ValueListenableProvider<int>(
        create: (_) => counter,
        child: _CounterText(),
      ),

      floatingActionButton: FloatingActionButton(
        onPressed: counter.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final number = Provider.of<int>(context);

    return Center(
      child: Text(number.toString()),
    );
  }
}

このサンプルでは使いませんでしたが、次の引数もあります。

  • updateShouldNotify
    • Provider.value() 等の updateShouldNotify と同様です。

おさらい(ChangeNotifierProvider vs ValueListenableProvider)

  • ChangeNotifierProvider
    • ChangeNotifier にも ValueNotifier にも使える
    • 下位の Widget に伝わるのはインスタンス
  • ValueListenableProvider
    • ChangeNotifier には使えない
    • 下位の Widget に伝わるのは ValueNotifier.value の中身

ValueListenableProvider.value()

ValueListenableProvider() / ValueListenableProvider.value() の違いは、Provider() / Provider.value() の違いと同じです。

value_listenable_provider_value.dart
class VlProviderValuePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = VnCounter();

    return Scaffold(
      body: ValueListenableProvider<int>.value(
        value: counter,
        child: _CounterText(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counter.increment,
        child: const Icon(Icons.add),
      ),
    );
  }
}

class _CounterText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final number = Provider.of<int>(context);

    return Center(
      child: Text(number.toString()),
    );
  }
}

引数は、v2 系で valueListenable だったのが v3.0.0 で value に変わりました。ご注意ください。

ListenableProvider()

ChangeNotifierProvider はこれを継承したものであり、似ています。

create では Listenable を生成します。
Listenable を実装しているものとして、ドキュメント には次のように4つが挙げられています。

Implementers
Animation, ChangeNotifier, CustomPainter, ValueListenable

このうちの ChangeNotifierValueListenable は既に見た ChangeNotifierProviderValueListenableProvider で扱えますね。
その二つにはこの ListenableProvider も使えます 20 が、特別な理由がなければあえて使う必要はないと思います。

使う場合、破棄の処理を任せたいなら dispose を指定する必要があります。
この点は ChangeNotifierProvider 等よりわずかに手間です。


2019/7/6

サンプルを載せていましたが、このプロバイダを使う明確な理由がなかったため削除しました。
説明文も更新しました。
GitHub に置いているサンプル では参考としてアニメーションに使っています。)

MultiProvider()

複数のプロバイダを使うときに

Provider<Foo>.value(
  value: foo,
  child: Provider<Bar>.value(
    value: bar,
    child: Provider<Baz>.value(
      value: baz,
      child: someWidget,
    )
  )
)

という深いネストにする代わりに

MultiProvider(
  providers: [
    Provider<Foo>.value(value: foo),
    Provider<Bar>.value(value: bar),
    Provider<Baz>.value(value: baz),
  ],
  child: someWidget,
)

のように簡潔に書けるようにするプロバイダです。
コードの見た目は異なりますが、Widget ツリー上は同じものになります。

※このコードは クラスのドキュメント から借用しました。

注意

上記の例では Provider<T>.value()T の部分に Foo Bar Baz という異なる型が指定されています。
もし同じ型にしてしまうと、次のように最後のプロバイダで指定した値になってしまいます。

MultiProvider(
  providers: [
    Provider<int>.value(value: 1),
    Provider<int>.value(value: 2),
    Provider<int>.value(value: 3),
  ],
  child: someWidget(context),
)
Widget someWidget(BuildContext context) {
   final val1 = Provider.of<int>(context);
   final val2 = Provider.of<int>(context);
   final val3 = Provider.of<int>(context);

   print(val1);  // 3
   print(val2);  // 3
   print(val3);  // 3
}

これは Provider.of() のドキュメント に書かれているとおり

Widget ツリーをさかのぼって最寄りの Provider<T> を取得し、その値を返す 21

という仕様によるものです。22
MultiProvider() に限ったことではありません。
ご注意ください。

FutureProvider()

ここからは v3.0.0 で追加された種類になります。
.value という名前付きのコンストラクタに関する説明はこの先省略します。


InheritedWidgetFutureBuilder を組み合わせたような便利なものです。
ここまでのサンプルと同じ動作にしたかったのですが、無理でしたので異なるものにしました。

flutter_future_provider.gif

  1. ページを表示すると「Wait for 3 seconds...」と表示される
  2. Future の処理が3秒後に完了する
  3. 自動的に表示が「3 seconds elasped.」に変わる
pages/future_provider.dart
class FutureProviderPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureProvider<String>(
        create: (_) =>
            Future.delayed(Duration(seconds: 3), () => '3 seconds elapsed.'),
        initialData: 'Wait for 3 seconds...',
        child: _FutureText(),
      ),
    );
  }
}

class _FutureText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final description = Provider.of<String>(context);

    return Center(
      child: Text(description),
    );
  }
}
  • initialData
    • Future の処理が完了していない間の初期値(省略時は null)。

このサンプルでは使いませんでしたが、次の引数もあります。

  • catchError
    • 他のプロバイダの同名の引数と同様です。
  • updateShouldNotify
    • 他のプロバイダの同名の引数と同様です。

FutureProvider.value() もありますが、省略します。

ProxyProvider()

下位 Widget で値を得る方法 のところで既に触れたものです。

これも同じ動作にするのが難しかったため、異なるサンプルにしました。
カウンターという点は同じですが、10進数と16進数の二つの表示になっています。

flutter_proxy_provider.gif

  1. 10進数と16進数のカウンターがある
    • 10進数は ValueListenableProvider() のサンプルで使ったものを流用。
    • 16進数のほうは10進数の値を受け取って保持する immutable なクラスであり、ゲッターで返すときに16進数に変換する。
  2. ChangeNotifierProvider() で10進数カウンターを生成
  3. ProxyProvider() で10進数の値を渡して16進数カウンターを生成
  4. 10進数カウンターの値が変わるたびに ProxyProviderupdate が呼ばれる
    • そのたびに16進数カウンターのインスタンスが生成される。
  5. 両カウンターのインスタンスが Widget ツリーの下位に伝播される
    • 10進数のカウントアップに連動して16進数の値も更新される。
models/hex_counter.dart
class HexCounter {
  int _decimal;

  HexCounter();

  set newValue(int newValue) => _decimal = newValue;

  String get hex => _decimal.toRadixString(16);
}
pages/proxy_provider.dart
class ProxyProviderPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<VnCounter>(
          create: (_) => VnCounter(),
        ),
        ProxyProvider<VnCounter, HexCounter>(
          create: (_) => HexCounter(),
          update: (_, decCounter, prevHexCounter) =>
              prevHexCounter..newValue = decCounter.value,
        ),
      ],
      child: Scaffold(
        body: _CounterResults(),
        floatingActionButton: _FloatingButton(),
      ),
    );
  }
}

class _FloatingButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<VnCounter>(context, listen: false);

    return FloatingActionButton(
      onPressed: counter.increment,
      child: const Icon(Icons.add),
    );
  }
}

class _CounterResults extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final decCounter = Provider.of<VnCounter>(context);
    final hexCounter = Provider.of<HexCounter>(context);

    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          const Text('Decimal'),
          Text(decCounter.value.toString()),
          const Text('Hexadecimal'),
          Text(hexCounter.hex),
        ],
      ),
    );
  }
}

create は一度しか呼ばれず、update は何度も呼ばれます。
前回に生成されたインスタンスを ProxyProvider の第3引数で受け取って再利用できるのですが、前回生成分がない初回のために create で先に生成しておくことができます。

なお、ChangeNotifierProxyProvider()ListenableProxyProvider() では、provider v3.1.0 において create が必須となりました。

※v3.1.0 時点では create ではなく initialBuilderupdate ではなく builder という引数名でした。
builder の呼ばれるタイミングが ProxyProvider だけ異なるというややこしさがありましたが、その後 v3.2.0 のリネームにより解消しました。

ChangeNotifierProxyProvider<Foo, MyChangeNotifier>(
  create: (_) => MyChangeNotifier(),
  update: (_, foo, myNotifier) => myNotifier
    ..foo = foo,
  child: ...
);

このコードは ChangeNotifierProxyProvider()ドキュメント からの引用したものです。
MyChangeNotifier のインスタンスを create で一度だけ生成しています。
update が呼ばれるたびに生成済みのインスタンスを第3引数で受け取って使い回すことになります。

InheritedProvider()

クラスのドキュメント に次のように書かれています。

Do not use this class directly unless you are creating a custom "Provider".
Instead use Provider class, which wraps InheritedProvider.

訳: カスタムなプロバイダを作る以外では InheritedProvider クラスを直接使わず、
   それをラップした Provider クラスを代わりに使うこと。

基本的に直接触る必要がないものです。

DI(依存オブジェクトの注入)

作者自身が言われているように、provider は DI にも使えるものです。5 6
この記事では省いてブログに書きました。

Locator

v4.1.0 で追加されました。
DI に使うことができます。

早速ですが用例を見てみましょう。

read()のドキュメントに書かれているLocatorの使用例
class Model {
  Model(this.locator);

  final Locator locator;

  void method() {
    print(locator<Whatever>());
  }
}

// ...

Provider(
  create: (context) => Model(context.read),
  child: ...,
)

これまでは Whatever のオブジェクトを Model 内で使うには、そのオブジェクト自体を Model に渡すか、BuildContext を渡して Model の中で Provider.of() を使って取り出す必要がありました(後者は Provider や Flutter に依存してしまうアンチパターン)。

一方 Locator を使うと、上記のように context.read を Model で受け取っておけば locator<Whatever>() で値にアクセスすることができます。

この Locator は次のように定義されていて、Flutter への依存がありません。
依存させたくない Model の中でも安心して使えることになります。

typedef Locator = T Function<T>();

ただし、これが便利かどうかは使用者の考え次第でしょう。23

トラブルシューティング

遷移先のページで値を得られない

解決方法は主に二つあります。

MaterialApp より上で渡す

MaterialApp より下で Provider で渡そうとしても次のページからは値にアクセスできません。
ツリーの一番上で渡せば問題に遭遇せずに済みます。

ページ遷移時に渡し直す

// A
Navigator.of(context).push(
  MaterialPageRoute<void>(builder: (context2) {
    // B
    return NextPage();
  }),
)

A 以上と B 以下のコンテキスト(context と context2)は異なるものです。
わかりやすく名前で区別していますが、名前だけでなく中身も異なります。
次のように A のコンテキストを使って受け取った値を B の位置で渡し直せば解決します。

Navigator.of(context).push(
  MaterialPageRoute<void>(
    builder: (context2) => Provider<Foo>.value(
      value: Provider.of<Foo>(context, listen: false),
      child: NextPage(),
    ),
  ),
)

ちょっと冗長な書き方になりますね。
ツリーの深いところまで渡す必要がなければ、プロバイダを使わずにコンストラクタに渡すほうがシンプルで良い場合もあります。

Navigator.of(context).push(
  MaterialPageRoute<void>(builder: (context) => NextPage(foo)),
)

コンストラクタ経由以外の渡し方 もあります。

initState() で値を得られない

class _FooPageState extends State<FooPage> {
  Bar _bar;

  @override
  void initState() {
    super.initState();
    _bar = Provider.of<Bar>(context);
  }

  ...
}

このように initState() にてプロバイダ経由で値を得ようとすると例外が発生します。

I/flutter ( 4953): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 4953): The following assertion was thrown building InheritedProvider<Bar>:
I/flutter ( 4953): inheritFromWidgetOfExactType(InheritedProvider<Bar>) or inheritFromElement() was called before
I/flutter ( 4953): _FooPageState.initState() completed.
I/flutter ( 4953): When an inherited widget changes, for example if the value of Theme.of() changes, its dependent
I/flutter ( 4953): widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor
I/flutter ( 4953): or an initState() method, then the rebuilt dependent widget will not reflect the changes in the
I/flutter ( 4953): inherited widget.
I/flutter ( 4953): Typically references to inherited widgets should occur in widget build() methods. Alternatively,
I/flutter ( 4953): initialization based on inherited widgets can be placed in the didChangeDependencies method, which
I/flutter ( 4953): is called after initState and whenever the dependencies change thereafter.

このエラーメッセージには答えがちゃんと書かれています。
特に最後の3行です。
build() 内か、initState() の後に呼ばれる didChangeDependencies() の中で行いましょう。

なお、didChangeDependencies() は initState() と違って何度も呼ばれるのでご注意ください。
State のライフサイクル(各メソッドが呼ばれるタイミング)については次のページがわかりやすいです。

provider 4.0.0 以降では listen: false を指定すれば initState() 内で使用できます。
ただし、リビルドが起こるようなメソッドをそこで使うことはできません。

@override
void initState() {
  super.initState();
  _bar = Provider.of<Bar>(context, listen: false);
  //_bar.someMethodToCauseRebuild(0);
}

build()内で値を変えるとエラー

Widget の build() 内で ChangeNotifierValueNotifier を使って値を更新すると起こる現象です。
例をご覧ください。

class FooModel extends ValueNotifier<String> {
  FooModel() : super('');
}
ChangeNotifierProvider<FooModel>(
  create: (_) => FooModel(),
  child: MyWidget(),
)
class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    final model = Provider.of<FooModel>(context);

    // build()内ですぐに値を変える
    model.value = 'Hello!';

    return Center(
      child: Text(model.value),
    );
  }
}
  1. FooModelvalueChangeNotifierProviderListen している
  2. 値が変わると、関連箇所をリビルドさせるために内部的に dirty という印が付けられる 24
  3. そのタイミングは Widget のビルド中であってはならず、違反すると次の例外が発生する
I/flutter (19781): ══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞════════════════════════════════════════════════════════
I/flutter (19781): The following assertion was thrown while dispatching notifications for FooModel:
I/flutter (19781): setState() or markNeedsBuild() called during build.
I/flutter (19781): This ChangeNotifierProvider<FooModel> widget cannot be marked as needing to build because the
I/flutter (19781): framework is already in the process of building widgets.  A widget can be marked as needing to be
I/flutter (19781): built during the build phase only if one of its ancestors is currently building. This exception is
I/flutter (19781): allowed because the framework builds parent widgets before children, which means a dirty descendant
I/flutter (19781): will always be built. Otherwise, the framework might not visit this widget during this build phase.
I/flutter (19781): The widget on which setState() or markNeedsBuild() was called was:
I/flutter (19781):   ChangeNotifierProvider<FooModel>
I/flutter (19781): The widget which was currently being built when the offending call was made was:
I/flutter (19781):   MyWidget

これを避ける方法は、Widget のビルド中に dirty を付けないことです。
そう考えると、代わりのタイミングは次の二つになります。

  • ビルドより前(祖先がビルドされている間)
    FooModel()value の変更によって dirty が付く Widget がビルドされる前という意味です。
    つまり ChangeNotifierProvider() より前である必要があります。
    ChangeNotifierProvider()create でも可能ですが、そこが OK なのは、その時点でまだビルドが始まっていないからだと思います。

  • ビルドが終わった後
    ChangeNotifierProvider() の子孫のビルド中に値を変えても例外が発生します。
    全子孫のビルドが終わったタイミングに処理をするには、下記のようなハックが必要です。25
    下記6つのうちどれを使っても、実行をビルド完了後に遅延させることができました。

Future(() { /* 処理 */ });
Future.delayed(Duration.zero, () { /* 処理 */ });
WidgetsBinding.instance.addPostFrameCallback((_) { /* 処理 */ });
// package:flutter/scheduler.dartのインポートが必要
SchedulerBinding.instance.addPostFrameCallback((_) { /* 処理 */ });
// dart:asyncのインポートが必要
scheduleMicrotask(() { /* 処理 */ });
Future.microtask(() { /* 処理 */ });

initState()didChangeDependencies()build() のどこに書いてもビルド後になります。

注意

  • 使う前によく検証しましょう。

  • initState() 以外はリビルドのたびに呼ばれ、値更新→リビルド→値更新… と繰り返してしまいます。
    フラグなどで制御する必要があると思います。

  • こちらのページ では WidgetsBinding を使えた人と使えなかった人がいます。

Selector() で複数の値を対象にしたい

公式ドキュメント では tuple を使うのが最も簡単な方法だと紹介されています。
次の例では foo.barfoo.baz の値が変わったときのみ builder が呼ばれます。

Selector<Foo, Tuple2<Bar, Baz>>(
  selector: (_, foo) => Tuple2(foo.bar, foo.baz),
  builder: (_, data, __) {
    return Text('${data.item1}  ${data.item2}');
  }
)

しかし、FAQ に書かれているように複数の値にそれぞれの型を持たせるのも良いでしょう。
FAQ の例では国名と市名を String ではなく Country と City という個別の型にすることで解決しています。

Provider<Country>(
  create: (_) => Country('England'),
  child: Provider<City>(
    create: (_) => City('London'),
    child: ...,
  ),
),

ダイアログでエラーが出る

ダイアログに限りませんが、Provider と異なるツリーで Provider.of() を使おうとすると例外が発生し、次のようなエラーメッセージが出力されます。

Tried to listen to a value exposed with provider, from outside of the widget tree.

もしそうなったら、Provider.of() で listen: false を指定してみましょう。

これで解決しない場合、似て非なる原因かもしれません。
メッセージの続きに対処法が書かれていないか確認してみてください。

Provider で生成したオブジェクトを直後の Provider で使いたい

create の関数に渡される BuildContext を使うと直前の Provider で生成されたオブジェクトにアクセスできます。

Providerをネストした場合
Provider<Foo>(
  create: (_) => Foo(),
  child: Provider<Bar>(
    create: (context) {  // このBuildContextを使う
      final foo = Provider.of<Foo>(context, listen: false),
      // または final foo = context.read<Foo>();
      return Bar(foo);
    },
    child: ...,
  ),
)
MultiProviderの場合
MultiProvider(
  Provider<Foo>(
    create: (_) => Foo(),
  ),
  Provider<Bar>(
    create: (context) {
      final foo = Provider.of<Foo>(context, listen: false);
      return Bar(foo);
    },
  ),
  child: ...,
)

  1. https://pub.dev/packages/provide "NOTE 2019-02-21: There's a discussion in the Flutter community over the difference between this package, package:provider, and package:scoped_model. There is a possibility that (some) of these efforts will merge. Learn more in issue #3." 

  2. https://www.youtube.com/watch?time_continue=1162&v=d_m5csmrf7I 19:23~19:33辺り "Fun fact: we actually had our own package, kind of like Scoped Model version 2. And then we released it earlier this year, open-sourced it everything, and then we realized provider is actually much better. So we're using provider instead. So don't use provide. Use provider." 

  3. https://pub.dev/packages/provider "provider is mostly syntax sugar for InheritedWidget, to make common use-cases straightforward." 

  4. https://github.com/rrousselGit/provider/issues/45#issuecomment-488241885 "This library is not opinionated on state management. It aims mostly at fixing the errors that the community make when working with inherited widgets." 

  5. https://pub.dev/packages/provider "A dependency injection system built with widgets for widgets." 

  6. https://www.reddit.com/r/FlutterDev/comments/bmrvey/so_is_provider_recommended_over_bloc_just_watched/emze9z6/ "What provider is all about is: having a really robust dependency injection system. It "forces" you to have a architecturally better app. No global, no messy object graph" 

  7. https://www.reddit.com/r/FlutterDev/comments/bmrvey/so_is_provider_recommended_over_bloc_just_watched/emze9z6/ "explicit creation/ dispose" 

  8. https://www.reddit.com/r/FlutterDev/comments/bmrvey/so_is_provider_recommended_over_bloc_just_watched/emze9z6/ "you can fit it pretty much anything. For example BLoC may use provider this way: ..." 

  9. https://github.com/rrousselGit/provider/issues/127#issuecomment-508954636 

  10. ProxyProvider 導入が検討されていたときの「[RFC] Simplifying providers that depends on each others #46」という Issue を見るとよりわかりやすいです。 

  11. そのエラーは assert() によるものなので Debug 時のみ起こります。Release ビルドでは起こりません。 

  12. read() の制約が厳しい理由を理解した上であれば、あえて制約のない context.value() (名前は適当)のような extension を自作して使うのは良いかもしれません。 

  13. リビルドは再描画ではないので、多少の無駄なリビルドが体感やバッテリ消費に大きく影響するものでもありません。 

  14. 更新履歴 には Builder と同じ振る舞いだと書かれていますが、少しだけ異なると思います。BuilderSelector 等では Baz はその孫なので Foo の値が更新されるたびにリビルドされますが、builder を使う場合は Baz は ChangeNotifierProvider の子であり、あらかじめビルドしてから使用されるのでリビルドされないはずです。 

  15. "It is usually used to avoid making a StatefulWidget for something trivial, such as instantiating a BLoC." 

  16. BLoC に特化した他の軽量なパッケージをあえて用いるのも良い選択だと思います。 

  17. .controller() ではなく .value() のほうについては、Firebase のユーザ認証に使う例が こちらの記事 に書かれています。プラグイン等によって用意される Stream をそのまま用いるようなケースに向いていそうです。 

  18. ソースコードをみると、create の処理が BuilderStateDelegate というものに移譲され、そこの dispose()Stream の破棄が行われているようです。このときに StreamController が破棄されるわけではないはずです。 

  19. ScopedModelDescendant() に近い書き方になるのは Consumer() ですが、もちろん Provider.of() も使えます。 

  20. ややこしいですが、ChangeNotifierValueListenableListenable の実装であり、ValueNotifierChangeNotifier を継承 & ValueListenable を実装したものです。また、ChangeNotifierProviderListenableProvider を継承しています。 

  21. "Obtains the nearest Provider<T> up its widget tree and returns its value." 

  22. 延いては、Provider.of() で使われている BuildContext.inheritFromWidgetOfExactType()BuildContext.ancestorInheritedElementForWidgetOfExactType() の仕様です。 

  23. 個人的には、Model がどのようなオブジェクトを使うのかが渡す側から見てわかりにくいと感じました。また、単体テスト等で TFunction<T>() 型にして渡す手間も生じます。一方で、依存が増えても渡す側を変更する手間をなくせます。そのようなメリットとデメリットのトレードオフになります。 

  24. Flutter in Focus の How Stateful Widgets Are Used Best - Flutter Widgets 101 Ep. 2 という動画が非常にわかりやすいです。 

  25. StackOverflow の ここここ が助けになりました。書かれている質問は別件だったりするのですが、回答のほうはビルド中の dirty 化の問題にも当てはまります。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした