Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
5
Help us understand the problem. What are the problem?

posted at

Providerの使い方を理解する。

Flutter Advent Calendar#3 14日目です。

Providerがピンとこない

Provider、状態管理という概念的にはなんとなくわかっても、巷に溢れているチュートリアルや解説を読んでも実装がいまいちがピンとこなかったので可能な限りシンプルに理解を試みてみました。
なお、InheritedWidgetは使ったことがありません。ScopedModelも知りません。BLoCという単語もさっぱり。
(たぶんこのせいで理解が及んでいない)

環境は以下の通りです。

Flutter 1.22.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 7891006299 (25 hours ago) • 2020-12-10 11:54:40 -0800
Engine • revision ae90085a84
Tools • Dart 2.10.4

Providerは、Provider: ^4.3.2+3となります。

素のプロジェクトをつくる

まず、プロジェクトを生成します。

$ flutter create myApp2Provider

$ cd myApp2Provider
$ flutter run

お馴染みの画面です。
Simulator Screen Shot - iPhone 12 Pro Max - 2020-12-12 at 06.32.12.png

これからコードを弄りますが、画面は一切変わらないのでスクリーンショットはこれだけになります。

Providerを導入

pubspec.yamlにパッケージを追加します。

pubspec.yaml
dependencies:
  provider: ^4.3.2+3

vscodeだと自動的に、flutter pub getが走ってくれます。
まず、パッケージをインポート。

main.dart
import 'package:provider/provider.dart';

ChangeNotifierを継承してオブジェクトクラスを定義。

main.dart
class MyModel extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    _counter++;
    notifyListeners();
  }
}

ここで元々、_MyHomePageStateクラスにあった_counterの状態管理をします。
値を更新した時は、notifyListeners()を呼ぶようです。

main()のrunAppにて、最上位のWidget(ここではMyApp)を包む形でChangeNotifierProviderを追加。

main.dart
 void main() {
-  runApp(MyApp());
+  runApp(ChangeNotifierProvider(create: (_) => MyModel(), child: MyApp()));
+}

_MyHomePageStateで管理していた_counterをコメントアウトし、カウンターの加算も書き換え。

main.dart
-  int _counter = 0;
+  //int _counter = 0;

   void _incrementCounter() {
+    /*
     setState(() {
       // This call to setState tells the Flutter framework that something has
       // changed in this State, which causes it to rerun the build method below
       // so that the display can reflect the updated values. If we changed
       // _counter without calling setState(), then the build method would not be
       // called again, and so nothing would appear to happen.
-      _counter++;
+      _counter++;      
     });
+    */
+
+    context.read<MyModel>().increment();
   }

context.read<MyModel>()で該当するクラスのインスタンスを取得できるようです。

更新に応じて表示される_counterも書き換えます。

main.dart
             Text(
-              '$_counter',
+              //'$_counter',
+              '${context.watch<MyModel>().counter}',
               style: Theme.of(context).textTheme.headline4,
             ),

更新を監視する時はcontext.watch<MyModel>()となるようです。
context.select<MyModel>()というのもあって、同じ監視でもMyModelの中味の一部のみを監視できるのだとか。今回は、_counterしかないんですけど。

これで動くはずです。全体の変更はこちら(GitHub)。

Widgetに手を入れる

もう少しパターンを試してみます。
counterの表示部分をWidgetに分離してみます。

main.dart
class Counter extends StatelessWidget {
  const Counter({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Text(
      '${context.watch<MyModel>().value}',
      style: Theme.of(context).textTheme.headline4,
    );
  }
}

MyAppの下位にあたるWidghtになるので(?)、contextからMyModelを参照できます。
MyHomePage::buildの表示部分もCounterに置き換えました。

main.dart
-            Text(
-              //'$_counter',
-              '${context.watch<MyModel>().counter}',
-              style: Theme.of(context).textTheme.headline4,
-            ),
+            Counter(),

ここで気がついたのは、StatefulWidgetではなくStatelessWidgetでよいということ。
MyHomePageStatelessWidgetに変更。

main.dart
-class MyHomePage extends StatefulWidget {
+class MyHomePage extends StatelessWidget {

そうすると、State管理する以下の行も全部不要になってしまいます。コメントアウト。

main.dart
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // int _counter = 0;

問題なく動作しました。
コメントアウトを整理した最終的なコード(GitHub)です。

まとまっていないまとめ

いちおう書いた範囲での使い方は理解したつもりになれました。

ただ、ChangeNotifierProviderを利用する手法はどうもscoped_model同名のパッケージのことか手法自体の呼称かは不明)の代わりに使う書き方のようでProviderの本懐ではないようです。
現時点で、ChangeNotifierProvider以外にもあるなんたらProviderがまるで理解の範囲外で、つまりProviderの使い方を理解したとは到底言えそうもありません。

書いて動かしてやっとわかったこともあって、
- ChangeNotifierProviderではなく、そもそもFlutterのクラス。
- 今回のサンプルのように更新する値がひとつならValueNotifier(== class ValueNotifier extends ChangeNorifier)でもよい。

とりあえずデータ(オブジェクト)を分離することができれば、これだけでもよりシンプルに書けるということはわかりました。
あくまで自分理解用の記事になってしまいましたが、どこかの誰かのお役に立てれば幸いです。

さて、長年放置している新刊.netアプリをつくらないと。

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
5
Help us understand the problem. What are the problem?