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
これからコードを弄りますが、画面は一切変わらないのでスクリーンショットはこれだけになります。
Providerを導入
pubspec.yaml
にパッケージを追加します。
dependencies:
provider: ^4.3.2+3
vscodeだと自動的に、flutter pub get
が走ってくれます。
まず、パッケージをインポート。
import 'package:provider/provider.dart';
ChangeNotifier
を継承してオブジェクトクラスを定義。
class MyModel extends ChangeNotifier {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners();
}
}
ここで元々、_MyHomePageState
クラスにあった_counter
の状態管理をします。
値を更新した時は、notifyListeners()
を呼ぶようです。
main()
のrunAppにて、最上位のWidget
(ここではMyApp)を包む形でChangeNotifierProvider
を追加。
void main() {
- runApp(MyApp());
+ runApp(ChangeNotifierProvider(create: (_) => MyModel(), child: MyApp()));
}
_MyHomePageState
で管理していた_counterをコメントアウトし、カウンターの加算も書き換え。
- 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++;
});
+ */
+ context.read<MyModel>().increment();
}
context.read<MyModel>()
で該当するクラスのインスタンスを取得できるようです。
更新に応じて表示される_counter
も書き換えます。
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
に分離してみます。
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
に置き換えました。
- Text(
- //'$_counter',
- '${context.watch<MyModel>().counter}',
- style: Theme.of(context).textTheme.headline4,
- ),
+ Counter(),
ここで気がついたのは、StatefulWidget
ではなくStatelessWidget
でよいということ。
MyHomePage
もStatelessWidget
に変更。
-class MyHomePage extends StatefulWidget {
+class MyHomePage extends StatelessWidget {
そうすると、State管理する以下の行も全部不要になってしまいます。コメントアウト。
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
// int _counter = 0;
問題なく動作しました。
コメントアウトを整理した最終的なコード(GitHub)です。
まとまっていないまとめ
いちおう書いた範囲での使い方は理解したつもりになれました。
ただ、ChangeNotifierProvider
を利用する手法はどうもscoped_model
(同名のパッケージのことか手法自体の呼称かは不明)の代わりに使う書き方のようでProvider
の本懐ではないようです。
現時点で、ChangeNotifierProvider
以外にもあるなんたらProvider
がまるで理解の範囲外で、つまりProvider
の使い方を理解したとは到底言えそうもありません。
書いて動かしてやっとわかったこともあって、
-
ChangeNotifier
はProvider
ではなく、そもそもFlutterのクラス。 - 今回のサンプルのように更新する値がひとつなら
ValueNotifier
(== class ValueNotifier extends ChangeNorifier)でもよい。
とりあえずデータ(オブジェクト)を分離することができれば、これだけでもよりシンプルに書けるということはわかりました。
あくまで自分理解用の記事になってしまいましたが、どこかの誰かのお役に立てれば幸いです。
さて、長年放置している新刊.netアプリをつくらないと。