懐かしいネタをDartで書かれている方がいた(こちらの記事)ので、自分もやってみました。
元ネタ
Javaの講義、試験が「自作関数を作り記述しなさい」って問題だったから
— てくも (@kumiromilk) March 9, 2016
「ズン」「ドコ」のいずれかをランダムで出力し続けて「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら「キ・ヨ・シ!」って出力した後終了って関数作ったら満点で単位貰ってた
完成品
コード全体
最初にコード全体を載せておきます。(import
文などは、省略しています。)
どうしてもネストが深くなりがちなFlutterのコードですが、
以下のように、__Widgetを細かくクラスに切り出す__ことで、見通しの良いコードになると思います。
// ViewModel
class Kiyoshi with ChangeNotifier {
String text = "";
void sing() {
if (text.endsWith("ズンズンズンズンドコ")) {
text = "キ・ヨ・シ!";
} else {
if (text == "キ・ヨ・シ!") text = "";
text += Random().nextBool() ? "ズン" : "ドコ";
}
notifyListeners();
}
}
// ページ
class KiyoshiPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Kiyoshi>(
create: (_) => Kiyoshi(),
child: Scaffold(
body: Center(child: KiyoshiText()),
floatingActionButton: KiyoshiButton(),
),
);
}
}
// ボタン
class KiyoshiButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final kiyoshi = Provider.of<Kiyoshi>(context, listen: false);
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => kiyoshi.sing(),
);
}
}
// テキスト
class KiyoshiText extends StatelessWidget {
@override
Widget build(BuildContext context) {
final kiyoshi = Provider.of<Kiyoshi>(context);
return Text(kiyoshi.text);
}
}
簡単な説明
ViewModelの構成
ChangeNotifier
をmixin
するだけで、ViewModelとして機能します。
ロジック実行後にnotifyListeners()
を呼ぶことで、__View側に変更を通知__できます。
ロジック自体は適当なので、説明は省略します。
class Kiyoshi with ChangeNotifier {
String text = "";
void sing() {
...省略
// View側に変更を通知
notifyListeners();
}
}
Widgetの構成
1. ViewModelをツリー上で使えるようにする
ChangeNotifierProvider
を使って、先ほど作ったKiyoshi
を注入します。
これで、KiyoshiPage
の下位ツリー全体で__同じKiyoshi
のインスタンスにアクセスできる__ようになります。
class KiyoshiPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<Kiyoshi>(
create: (_) => Kiyoshi(),
child: ...
);
}
}
2. テキスト部分を作る
Provider.of()
を使うことで、先ほど注入したKiyoshi
のインスタンスを*O(1)*で取得できます。
この時、このWidgetはリスナーとして登録され、notifyListeners()
が呼ばれる度に自動でリビルドされるようになります。
class KiyoshiText extends StatelessWidget {
@override
Widget build(BuildContext context) {
final kiyoshi = Provider.of<Kiyoshi>(context);
return Text(kiyoshi.text);
}
}
また、以下のように、Consumer
を使って書くこともできます。(こちらのほうが一般的かも)
class KiyoshiText extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<Kiyoshi>(
builder: (context, kiyoshi, child) {
return Text(kiyoshi.text);
},
);
}
}
3. ボタン部分を作る
やっていることは、テキスト部分と同じです。
ただし、ボタンはテキストと違い毎回リビルドする必要はないので、listen: false
を設定しています。
class KiyoshiButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ↓ココ重要
final kiyoshi = Provider.of<Kiyoshi>(context, listen: false);
return FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => kiyoshi.sing(),
);
}
}
ChangeNotifierを使った感想
1. かんたん
ReduxやBLoCパターンと比べると、圧倒的にわかりやすいです。
また、ボイラープレートがほとんどなく、サクサク書けます。
2. 使ってる人が多い
Flutterのステートマネジメントの中では、かなり人気の構成です。
検索すれば、たくさん記事やサンプルコードが出てきます。(まだまだ日本語記事は少ないですが)
3. 他のアーキテクチャと併用も良さそう
自分は普段、BLoCパターンを使っているのですが、
基本はBLoCパターンで実装して、Themeの切り替えなど、単純なロジックのみProvider
+ ChangeNotifier
で実装といった構成も有りかなと思いました。
ChangeNotifierを使う際の注意点
公式リファレンスを読むと、
ChangeNotifier is optimized for small numbers (one or two) of listeners. It is O(N) for adding and removing listeners and O(N²) for dispatching notifications (where N is the number of listeners).
と書かれています。
翻訳にかけると、
ChangeNotifierは、少数(1つまたは2つ)のリスナー用に最適化されています。リスナーを追加および削除する場合はO(N)、通知をディスパッチする場合はO(N²)です(Nはリスナーの数です)。
だそうです。
つまり、__「listenするWidgetの数が多い場合には適していない」__ようなので注意が必要です。