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 を使うことで得られるメリットは
- StatefulWidget を使う必要がなくなる ( setState の更新は不要)
- iOS でいうところの RxSwift の使い方に似ている
- 末端の widget まで値を送る必要がなくなる
これらだと思っている。
それではこれを前提に 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() で Scaffold を包みました。
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 して値を通知するところ
みたいなイメージです。
そんな印象でした。今日はここまで。
それでは、バイバイ!