📞

ChangeNotifierProviderとは😳

2022/06/05に公開

みたことあるけど使ったことなかったので勉強してみた。

公式ドキュメント
https://pub.dev/packages/provider

他のProviderについて解説されているドキュメント
https://pub.dev/documentation/provider/latest/provider/provider-library.html#classes

翻訳した文章
ChangeNotifier 用の ListenableProvider の仕様です。必要なときに自動的にChangeNotifier.disposeを呼び出します。

https://pub.dev/documentation/provider/latest/provider/ChangeNotifierProvider-class.html

使い方はどうすれば良いのか?
翻訳してみる。

オブジェクトの作成にProvider.valueを使用しないでください。「どゆこと?」

ChangeNotifierProvider.value(
  value: MyModel(),
  child: ...
)

既存のオブジェクト・インスタンスを再利用する。
すでにオブジェクトのインスタンスを持っていて、それを公開したい場合、プロバイダの .value コンストラクタを使用するのが最善でしょう。

そうしないと、オブジェクトがまだ使用されているときに dispose メソッドを呼び出す可能性があります。

既存のChangeNotifierを提供するために、ChangeNotifierProvider.valueを使用してください。

MyChangeNotifier variable;

ChangeNotifierProvider.value(
  value: variable,
  child: ...
)

デフォルトのコンストラクタで既存のChangeNotifierを再利用しないでください。

MyChangeNotifier variable;

ChangeNotifierProvider(
  create: (_) => variable,
  child: ...
)

これだけでは、理解できないので動くプログラムを作ってみた!

今回は、昔からある書き方で、Provider.of<T>(context).valueを使ってみた!
最近は、context.watch<T>().valueとが使われいるようですが...

フォルダ構成

lib
├── expoert.dart
├── main.dart
└── provider
    └── text.dart

import文を使いませるように書いておいた。ページを分けていたのだが、うまくいかなったので、main.dartに書いた😅
何度も呼び出すのが面倒臭いときはこの書き方を使います。

export.dart

export 'package:flutter/material.dart';
export 'package:provider/provider.dart';
export 'package:provider_context/provider/text.dart';

provider/text.dart

import 'package:flutter/material.dart';
// ChangeNotifierは、class内の値が変更した場合に知らせる機能を付与するという意味
class TextProvider extends ChangeNotifier {
  // フォームから入ってくる値を格納する変数を定義
  String textValue = 'text';
  // 関数に「String型のtext」という引数を書く
  void TextChanged(String text) {
    // 上で定義した「textValue」という変数を呼び出して、関数の中に書いておく。この変数の中に、
    //関数の引数を格納しておく。
    // 上で定義した変数 = 引数
    textValue = text;
    // ChangeNotifierを使用しているclassに使用できる関数、値が変わったことを他のページにも知らせて更新させる役目をもっている
    notifyListeners();
  }
}

main.dart

import 'expoert.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // Page1をラップする。text.dartで作成した値や関数を呼び出せる
      // ChangeNotifierProvider<他のページに書いたモデルのクラス名>と書く
      home: ChangeNotifierProvider<TextProvider>(
          create: (context) => TextProvider(), child: Page1()),
    );
  }
}

class Page1 extends StatelessWidget {
  const Page1({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // Provider.of<他のページに書いたモデルのクラス名>(context).定義した変数「(例)final String 変数名」
        title: Text(Provider.of<TextProvider>(context).textValue),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This,
      body: Column(
        children: [
          Page2(),
          Page3()
        ],
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  
  Widget build(BuildContext context) {
    // TextButtonを押した時やTextFieldで値が変わる場合(listen: false)を追記する必要がある
    return TextField(
      onChanged: (newText){
        // textValue = newText
        Provider.of<TextProvider>(context, listen: false).TextChanged(newText);
        print(Provider.of<TextProvider>(context, listen: false).textValue);
      },
    );
  }
}

class Page3 extends StatelessWidget {
  
  Widget build(BuildContext context) {
    // 保存された文字を画面に表示する
    return Text(Provider.of<TextProvider>(context).textValue);
  }
}

buildしてみる

logの出力結果

H
flutter: He
flutter: Hel
flutter: Hell
flutter: Hello
flutter: Hello
flutter: Hello W
flutter: Hello Wo
flutter: Hello Wor
flutter: Hello Worl
flutter: Hello World

onChangedだと、入力されるたびに実行されていますね。textEditindControllerと異なるのは、ここですね。

やってみた感想

Githubのソースコードで、Flutterのサンプルないかと探すとこの書き方に遭遇することが多いので覚えておいた方がいいなと最近痛感しております😇
riverpodが流行っているそうですが、Providerもまだ使われていると思うので、状態管理はProviderからやった方が良いと思います。

おまけ

Firebaseに値を保存する処理を作ってみました!
動作は保証しませんので、参考までに...

Githubのリポジトリ
https://github.com/sakurakotubaki/ProviderSendMessage

スクリーンショット

追加情報

ドキュメントの翻訳をより詳しくをやってみた。正解かわかりませんが...

ChangeNotifierをリッスンし、その子孫に公開して、ChangeNotifier.notifyListenersが呼び出されるたびに依存関係を再構築します。

「listenとは、聞くという意味でした!」

モデルとなる親の状態を子供が継承して、その子供(孫)が継承して使うことができる?そんな風に解釈をしております。FlutterのWidgetはツリー構造になっているので、上から下にプログラムが実行されるので、
parent-> child-> children->....こんなイメージだと思います😅

Provider.of(context)は何をしているのか?

※値の変更を監視している📹
値を監視ししたくないときは、Provider.of(context, listen: false)と書く!

参考になった解説と図があった昔の記事
https://itome.team/blog/2019/12/flutter-advent-calendar-day7/

Discussion