Closed2

【Flutter】ChangeNotifier, ChangeNotifierProvider, Consumer, Provider.Ofメモ

Ryouhei FurugenRyouhei Furugen

状態を管理するときに便利なProvider

アプリの状態をさまざまな場所から変更する必要がある場合、大量のコールバックを渡さなければなりません。
provider を使えば便利に解決できる。
3つのコンセプトを理解する必要があります。

  • ChangeNotifier
  • ChangeNotifierProvider
  • Consumer

ChangeNotifier


ChangeNotifierの役割

ChangeNotifierは、Flutter SDKに含まれるシンプルなクラスで、リスナーに変更通知を提供する。
監視可能にさせるクラス。ChangeNotifierをextendsしているクラスは、インスタンスの中のメソッドが実行されるとchangeNotifierで知らせることができるようになる。
ChangeNotifierに固有のコードは、notifyListeners()の呼び出しだけです。アプリのUIを変更するようなモデルの変更があった場合は、このメソッドを呼び出す。

ChangeNotifierでカプセル化できる

providerでは、ChangeNotifierは、アプリケーションの状態をカプセル化する一つの方法でもある。
(providerChangeNotifierを使う必要はありませんが、簡単に使えるクラスです)

class CartModel extends ChangeNotifier {
  /// カートの内部、プライベートな状態
  final List<Item> _items = [];

  /// カートの中のアイテムを変更できないように表示します。
  // privateな変数_itemを含むUnmodifiableListView取得するためのgetter
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);

  /// すべてのアイテムの現在の合計金額(すべてのアイテムが42ドルであると仮定した場合)。
  int get totalPrice => _items.length * 42;

  /// [item]をカートに追加します。これと[removeAll]は、
  /// 外部からカートを変更する唯一の方法です。
  void add(Item item) {
    _items.add(item);
   // この呼び出しは、このモデルを聴いているウィジェットに再構築を指示します。
    notifyListeners();
  }

  /// Removes all items from the cart.
  void removeAll() {
    _items.clear();
    // This call tells the widgets that are listening to this model to rebuild.
    notifyListeners();
  }
}

ChangeNotifierProvider


役割

ChangeNotifierProviderは、ChangeNotifierのインスタンス(モデル)をその子孫に提供するウィジェット。
モデルを使いたいWidgetの上に置く。ただし、必要以上上には置かない。

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CartModel(),
      child: const MyApp(),
    ),
  );
}

Consumer


目的:

ChangeNotifierProviderで提供されたモデルを使うためのWidget
これは、Consumerウィジェットを通じて行われます。

return Consumer<CartModel>(
  builder: (context, cart, child) {
    return Text("Total price: ${cart.totalPrice}");
  },
);

使い方

  • Consumerではアクセスしたいモデルのタイプを指定する必要があります。
  • Consumerウィジェットの唯一の必須引数はbuilderです。Builderは、ChangeNotifierが変更されるたびに呼び出される関数です。(言い換えれば、モデルでnotifyListeners()を呼び出すと、対応するすべてのConsumerウィジェットのbuilderメソッドが呼び出されます)

builderは3つの引数で呼び出されます。

  • 最初の引数はcontextで、これはすべてのbuildメソッドでも取得できます。
  • ビルダー関数の第2引数は、ChangeNotifierのインスタンスです。これは最初に求めていたものです。モデルのデータを使って、任意の時点でのUIがどうあるべきかを定義することができます。
  • 3番目の引数はchildですが、これは最適化のためにあります。Consumerの下に、モデルが変更されても変更されない大きなwidgetサブツリーがある場合、それを一度構築して、ビルダーを介して取得することができます。
return Consumer<CartModel>(
  builder: (context, cart, child) => Stack(
    children: [
      // ここでSomeExpensiveWidgetを使うと、毎回再構築する必要がありません。
      if (child != null) child,
      Text("Total price: ${cart.totalPrice}"),
    ],
  ),
  // Build the expensive widget here.
  child: const SomeExpensiveWidget(),
);
  • Consumerウィジェットは、できるだけツリーの深いところに配置するのがよいでしょう。どこかの詳細が変更されたからといって、UIの大部分を再構築することは避けたいものです。
// DON'T DO THIS
return Consumer<CartModel>(
  builder: (context, cart, child) {
    return HumongousWidget(
      // ...
      child: AnotherMonstrousWidget(
        // ...
        child: Text('Total price: ${cart.totalPrice}'),
      ),
    );
  },
);

Instead:

// DO THIS
return HumongousWidget(
  // ...
  child: AnotherMonstrousWidget(
    // ...
    child: Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text('Total price: ${cart.totalPrice}');
      },
    ),
  ),
);

必要なところのみconsumerを使う。

Provider.of


モデルの使うためのWidget。

Provider.of<CartModel>(context, listen: false).removeAll();

Consumerとの違い。
UIの変更などで、モデルのデータが必要ない場合(例えばカートの内容削除とか)に
listenパラメータをfalseにして、メソッドのみ呼び出すといい。
無駄なwidget の再構築がなくて済む。
上記の行をビルドメソッドで使用すると、notifyListenersが呼ばれたときに、このウィジェットが再構築されません。

参考

https://docs.flutter.dev/development/data-and-backend/state-mgmt/simple#providerof

https://www.youtube.com/watch?v=iN2IjSQR7Fs&t=1028s

Ryouhei FurugenRyouhei Furugen

状態とは

SPAやモバイルアプリなど昨今のフロントエンド開発において、フロントエンド側は多くの「状態」を保持し、「状態」に応じてUIを変化させます。

状態とは一言で言えば「アプリケーションが保持するデータ」のことですが、
・APIを通じて取得したサーバのデータ
・フォームに入力した文字列
・モーダルが開いている・閉じている
などサーバから取得した状態やUIに閉じた状態など様々なデータが含まれます。

【2021年版】Flutterの状態管理パターン総まとめ - Qiita

このスクラップは2022/03/18にクローズされました