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アプリをつくらないと。
