StatefulWidgetとStatelessWidgetの違いを記事にしましたが、ビジネスロジックと表示関係を分けてソースコードを管理したい場合に、StatefulWidgetを使うと難しくなってしまいます。
そこで、Providerパターンを使うと良いらしいです。
ちなみに◯◯パターンというのはたくさんあって、オールドプログラマーである私は、つい最近まで知らなかったのですが、21世紀の今はロジックを一から考えるのではなく、ある程度パターン化されてきているので、それを◯◯パターンという1つの型にして、定義したようなものです。
これをデザインパターンと言います。
デザインパターンを覚えておくと、「このロジックは、この◯◯パターンを使えるな」とか技術者同士での話が早くなるのだそうです。キン肉マンで言うところの、プリンスカメハメに教えてもらった48の殺人技みたいなもんかなw
Providerパッケージの導入と、Modelの分離の仕方もKboyさんの動画で勉強になりました。
実行結果の画面はこちら
Flutterの新規プロジェクト作成時に生成されるデフォルトのサンプルコードに少し手を加えて、main.dartをmain_modelと分離しています。
デフォルトでは、FloatingActionButtonはScaffoldの引数として用意されていましたが、modelのメソッドを呼び出すため、Customer内に移動させ、インクリメント用のボタンに加えて、デクリメント用のボタンを用意してみました。
パッケージ導入方法は、もう腐るほどやっているので割愛して、ソースだけ掲載します。
※「◆◆◆」がついているコメントは、ProviderでModel化に関わる部分です。
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'main_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// ◆◆◆変更通知提供者で包括し、変更監視対象はMainModelとする
home: ChangeNotifierProvider<MainModel>( // from Widget
// ◆◆◆変更監視対象のインスタンスを作成する
create: (_) => MainModel(),
child: Scaffold(
appBar: AppBar(
title: const Text('Flutter Demo Provider'),
),
// ◆◆◆変更通知先である顧客を用意し、変更監視対象はMainModelとする
body: Consumer<MainModel>( // from StreamBuilder
// ◆◆◆modelのインスタンスを受け取って使用する
builder: (context, model, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
// ◆◆◆変更監視対象のmodelのカウンタを表示する
model.pushCounter.toString(),
style: Theme.of(context).textTheme.headline4,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FloatingActionButton(
// ◆◆◆押下時、変更監視対象のmodelのメソッドを実行する
onPressed: model.decrementCounter,
tooltip: 'Decrement',
child: const Icon(Icons.remove),
backgroundColor: Colors.red,
),
FloatingActionButton(
// ◆◆◆押下時、変更監視対象のmodelのメソッドを実行する
onPressed: model.incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
],
),
],
),
);
}
),
),
),
);
}
}
import 'package:flutter/material.dart';
/// ◆◆◆ChangeNotifierを継承したモデルクラス
class MainModel extends ChangeNotifier {
/// コンストラクタ
MainModel() {
// NOP
}
int pushCounter = 0;
/// カウンタをインクリメントするメソッド
void incrementCounter() {
pushCounter++;
// ◆◆◆変更を通知する
notifyListeners();
}
/// カウンタをデクリメントするメソッド
void decrementCounter() {
pushCounter--;
// ◆◆◆変更を通知する
notifyListeners();
}
}
ProviderパターンによるModel化のポイント
model側では、ChangeNotifierを継承して、明示的に通知メソッドであるnotifyListenersを呼び出しているので、標準のstatefulWidgetのsetStateメソッドを使うよりも分かりやすい気がする。
対してmain側では、Scaffoldを包み込む形で、ChangeNotifierProviderが用意されている。そしてScaffoldのbody引数に、Consumerを用意してmodelのインスタンスを扱っている。
main側が少々ややこしく見えるけど、こう考えると分かりやすいかな?
デパートなどのお店がChangeNotifierProviderで、館内放送でズボンの裾上げなど商品が仕上がるのを通知する。この「商品の仕上がり」がMainModelに相当する。
Consumerはそのお店に来ているお客さんで、商品の出来上がり(MainModel)を待っている。
お客さん(Consumer)は、お店(ChangeNotifierProvider)の中に居ないと館内放送で通知を受け取れない。
館内放送は何にでも対応していて、今回はズボンの裾上げで商品が仕上がるの(MainModel)を通知してるが、迷子やタイムセール、イベントなどをお知らせすることもできる。なのでジェネリック型で<MainModel>としている。
オールドプログラマーとしては、先進的なオブジェクト指向を理解しにくいですが、こんな感じで一つ一つ紐解いて理解に努めますw