64. FlutterのProviderパターンを3分で理解する
FlutterのProviderパターンを3分で理解する
Flutter 初心者にとって Provider の扱いはとても難しく思うはず。
僕も例外なく Flutter 学習初めの頃は Provider を見てもあまり魅力を感じなかった。
今回はその Provider について理解できるようにすることが主目的である。
【目次】
事前知識
Provider の書き方を理解する上で、前提知識といいますか経験値が必要な気がします。
- Container, Column, Row, ListView を使ってレイアウトを組み立てられる
- StatefulWidget の setState の使い方を理解している
- Widget の分割のメリットを理解している
多分、これらまで理解していたら Provider で今よりもいい感じの設計ができる(はず)。
Providerとは
Provider の概念・理念の理解は itome さんのブログが一番分かりやすいと思う。
この記事を読めば Provider については理解できるはずだった。はずだったというのは僕が例外だったから。
多分、本当に初心者の方にとっては Provider の理解は難しいと思う。
Provider の特徴
完全に Providerについて理解していないので、勿論間違った解釈をしているかもしれない。
そのレベルですが、 Provider を使うことで得られるメリットは
これらだと思っている。
それではこれを前提に Provider のついて学習していきたい。
サンプルコード
Provider のサンプルコードについては上記のページと同じコードを載せることになります。
最初は Flutter の初期コードをそのままカスタマイズするためです。
それでは Flutter の初期コードについてコメント文を削除した状態を載せます。
main.dart
import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
こちらが初期コードになります。
特徴は
- setState で値を更新している (StatefulWidget のため)
- Widget の生成 と Model が同じクラス内で行われている
です。
これをProviderパターンに書き換えます。
Provider の手順について
それでは Provider を使う手順について紹介します。
- Provider をインストールする
- ChangeNotifier を継承した Model クラスを定義する
- 発火したいイベントを持っている widget にConsumer で包む
- Provider で値を更新させたい widget に ChangeNotifierProvider で包む
- 値を更新したい箇所 に Provider で包む
1. Provider をインストールする
Provider パッケージのページはこちらになります。
yaml ファイルに宣言してインストールします。
pubspec.yaml
dependencies: flutter: sdk: flutter provider: ^4.1.2
使うファイルで import します。
main.dart
import 'package:provider/provider.dart';
これで準備が整いました。
2. ChangeNotifier を継承した Model クラスを定義する
次に ChangeNotifier を継承した Model クラスを定義します。
main.dart
import 'package:provider/provider.dart'; class CountModel extends ChangeNotifier { /// 初期値 int count = 0; /// count の更新メソッド void increment() { count ++; notifyListeners(); } }
こんな感じでOKです。
3. 発火したいイベントを持っている widget に Consumer で包む
次に値を更新するイベントを持っているwidget を ChangeNotifierProvider で包んであげます。
ですが、最初のコードは StatefulWidget で書かれているので StatelessWidget に書き直して
Widget build(BuildContext context) の中身を移動させます。
main.dart
class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), // This trailing comma makes auto-formatting nicer for build methods. ); } }
この時点では counter や incrementCounter が存在しないのでエラーになります。
まずは Scaffold の部分を次のように変更します。
child: Consumer<CountModel>( builder: (context, model, child) => Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), /* * 以下省略 */ ) )
Consumer
4. Provider で値を通知したい widget に ChangeNotifierProvider で包む
次に値を更新させたい widget を ChangeNotifierProvider で包みます。
今回は MyHomePage クラスの Scaffold に対して値を通知したいです。
main.dart
return ChangeNotifierProvider<CountModel>( create: (context) => CountModel(), child: Consumer<CountModel>( builder: (context, model, child) => Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), /* * 以下省略 */ ) ), );
5. 値を更新したい箇所 に Provider で包む
最後に更新された値を受け取ってそれを widget に反映させます。
値を提供するということで Provider と名付けられているかもしれません。
main.dart
Text( /// Provider を使う '${Provider.of<CountModel>(context).count}', style: Theme.of(context).textTheme.headline4, )
これで Consumer で包んだ FloatingActionButton をタップした時にProviderで受け取った Text の数値が変更されます。
またこれはwidget で切り離して使うこともできます。
class CountText extends StatelessWidget { @override Widget build(BuildContext context) { return Text( /// context からModelの値が使える '${Provider.of<CountModel>(context).count}', style: Theme.of(context).textTheme.headline4, ); } }
なんと、model の count が Provider.of からシングルトンのように取得できるようになりました。
わざわざ count のプロパティを受け取る必要がないことがわかります。
これでビルドすると Flutter プロジェクトが作成された初期画面と同じ挙動になります。
全体のソースコード
最後に全体のソースコードを載せておきます。
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; class CountModel extends ChangeNotifier { /// 初期値 int count = 0; /// count の更新メソッド void increment() { count++; notifyListeners(); } } void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, visualDensity: VisualDensity.adaptivePlatformDensity, ), home: MyHomePage(), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider<CountModel>( create: (context) => CountModel(), child: Consumer<CountModel>( builder: (context, model, child) => Scaffold( appBar: AppBar( title: Text('Flutter Demo Home Page'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text( 'You have pushed the button this many times:', ), CountText(), ], ), ), floatingActionButton: FloatingActionButton( onPressed: model.increment, tooltip: 'Increment', child: Icon(Icons.add), ) ) ), ); } } class CountText extends StatelessWidget { @override Widget build(BuildContext context) { return Text( /// context からModelの値が使える '${Provider.of<CountModel>(context).count}', style: Theme.of(context).textTheme.headline4, ); } }
ということで長かったですが、最後にこのパターンを暗記します。
一回テンプレート的に書き方を覚えたら後は応用になるからです。
ちなみに、この Provider パターンは、
RxSwift に例えるとするなら、
Consumer は BehavorRelay の accept で値を渡すところ
Provider は Observable で値を通知するところ
iOS でいうならば、
Consumer は NotificationCenter をpostするところ
Provider は NotificationCenter でadd して値を通知するところ
みたいなイメージです。
そんな印象でした。今日はここまで。
それでは、バイバイ!