はじめに
いま、Flutter界隈では、「state_notifier + freezed」を使って開発するのがアツい! と、評判です。
Flutter state_notifierいい感じなので使ったほうが良いですよ
https://qiita.com/_masaokb/items/fe77495db0aeba226d2a
「state_notifier」は、providerと組み合わせて使い、Widgetから**「状態」と「ロジック」を簡単に分離し&通知する**ことができるライブラリです。Widgetの状態管理を楽にしてくれたり、無駄なリビルドを抑制したりしてくれます。
「freezed」は、State(状態)を「データを保持するだけのクラス」として変更不可(イミュータブル)なクラスとして表現することができます。
Flutter界隈のエンジニアの方々もオススメされているので、「流行ってるみたいだし、めっちゃよさげだし、私も乗り遅れないようにしないと」と思いました。
次にやるFlutter案件
— 文字数カウントメモ開発者🎉 takashi (@cloverkizuna) May 15, 2020
・state_notifier
・freezed
を使うことにした🙂
state_notifierとfreezedパッケージで何かアプリ作ってみようかな!
— Shogo🏝宮崎 | 対局時計さん⚫️⏰⚪️ (@shogo0525) May 17, 2020
StateNotifierを使ったFlutterのアプリ設計 https://t.co/bDrDGkVopv
Flutter state notifier と freezed の おかけで 死ぬほど楽
— 奥村晋太郎 (@120921Shin) May 12, 2020
どっかのタイミングでこの知見は共有したい
この前リリースしたアプリがstate_notifierとfreezedで作ってるんだけど、書いてて結構やりやすいと思った。
— としのぶ@個人開発中🚲 (@Toshinobu724) May 19, 2020
けど、あの書き方であってるのかがわからない
みんなどうやってstate_notifier使ってるんだろ
state_notifierパッケージがとても良いので、それベースに変えた( ´・‿・`)基本は同じだけど( ´・‿・`)
— mono 🎯 @自宅 💙 (@_mono) March 10, 2020
- StateNotifierを基本的に常時利用
- StateNotifierが肥大化したら適宜、子StateNotifier的なものに分割して移譲
- stateクラスは基本的にfreezed利用
- 基本的な扱い方は変わらず https://t.co/e1C56Es4mp
私も、実際に使ってみて、めちゃくちゃ良かった ですし、オススメ です。
そこで本記事では、「state_notifier + freezed」について、カウンターアプリを作りながら説明します。
本記事のコードについて、以下のGitHubリポジトリに公開しています。
https://github.com/karamage/flutter_state_notifier_freezed_sample
state_notifier について
https://pub.dev/packages/state_notifier
state_notifier は、providerでの状態管理 を、より簡単に、楽にするライブラリです。
Flutterで、状態管理のパターンとして、以下のものがよく使われています。
- setState(StatefulWidget)
- Redux
- BLoC(Stream + InheritedWidget/Scoped Model)
- provider(ChangeNotifier)
現時点で、Flutterの状態管理において ベストプラクティスは、providerを使うこと だと思います。
(※個人の感想です。Flutterは進化が早いし、流行り廃りが激しい分野なので、これからどうなるかはワカランです。)
Google公式においても、providerの使用を推奨しています。
(Pragmatic State Management in Flutter (Google I/O'19))
state_notifierは、providerをより使いやすくコードをスッキリと楽に書けるようにしてくれるパッケージです。
従来のproviderパターンにおいて、ChangeNotifierを使ってゴリゴリとコードを書いていたのですが、コードが少々冗長になり、Widget階層が深くなりがちな問題があります。
state_notifierベースでproviderのコードを書くと、
ChangeNotifierを使用した場合よりコードがスッキリし、
コードを書いていて気持ちが良いです。
私は、これまでChangeNotifierベースでコードを書いていたのですが、
これからはstate_notifierベースに書き換えていこうと思います。
freezed について
https://pub.dev/packages/freezed
freezedパッケージは、イミュータブルなデータモデルを作成するのに使います。
Stateは、データを保持するだけのクラスにしたいので、 変更不可(イミュータブル/freezed)なクラスにします。
state_notifierでは、State(状態)をイミュータブルなデータモデルとして扱います。
state_notifierとfreezedは相性が良く、セットで使うのが推奨されます。
freezedを使うと、StateにcopyWith(clone)メソッドが自動的に生えるので、便利です。
state_notifier と freezed をパッケージインストール
pubspec.yaml を以下のように書きます。
dependencies:
flutter:
sdk: flutter
provider: ^4.1.0-dev
state_notifier: ^0.4.0
flutter_state_notifier: ^0.3.0
freezed_annotation:
dev_dependencies:
flutter_test:
sdk: flutter
json_serializable:
build_runner:
freezed:
json_serializableは、今回のアプリではJSONを扱わないので入れなくても良いのですが、JSONの扱う際にfreezedとセットでいれとくと便利です。
ターミナルで以下のコマンドを叩くと、state_notifier と freezed パッケージのインストールが完了します。
flutter pub get
カウンターアプリを実装する
ここで、flutter createした際のボイラーテンプレートのカウンターアプリを、「state_notifier + freezed」を使用して実装してみます。
CounterStateとCounterStateNotifierを定義する
新規ファイル「counter_state.dart」 を作成し、以下のコードを記述してください。
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:state_notifier/state_notifier.dart';
part 'counter_state.freezed.dart';
part 'counter_state.g.dart';
@freezed
abstract class CounterState with _$CounterState {
const factory CounterState({
@Default(0) int count,
}) = _CounterState;
factory CounterState.fromJson(Map<String, dynamic> json) => _$CounterStateFromJson(json);
}
class CounterStateNotifier extends StateNotifier<CounterState> {
CounterStateNotifier() : super(const CounterState()) {}
increment() => state = state.copyWith(count: state.count + 1);
}
その後、ターミナルで、以下のコマンドを叩いて、「counter_state.freezed.dart」「counter_state.g.dart」を自動生成してください。
flutter pub run build_runner build
counter_state.dart では、「CounterState」
クラスと「CounterStateNotifier」
クラスを定義しています。
CounterState
CounterStateは、状態データの入れ物で、イミュータブルなデータクラスとして定義します。
カウンターの値の状態変数として「int count」
が定義されています。
デフォルト値を設定するには「@Default(0) int count」
のように記述します。
「@freezed」
アノテーションをつけることによって、copyWithメソッドが自動で生えます。copyWithメソッドは状態を更新する場合に、新しく状態を生成する(clone)ために使います。
「CounterState.fromJson」
メソッドは、今回のカウンターアプリでは使用しませんが、JSONからStateを生成する場合によく使うので記述しています。
CounterStateNotifier
CounterStateNotifierは、状態を操作するロジックを管理し、Widgetに状態の変更を通知します。
MVVMモデルで解釈すると、**「ViewModel」**に相当します。
「inclement」
メソッドは、CouterStateのcountを1カウントアップした状態をcopyWithメソッドを使用して、新しく状態を作成し、更新しています。
CounterStateNotifierを使用したWidgetを作成する
main.dartに以下のコードを記述します。
import 'package:flutter/material.dart';
import 'package:flutter_state_notifier/flutter_state_notifier.dart';
import 'package:provider/provider.dart';
import 'package:state_notifier_example/counter_state.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: StateNotifierProvider<CounterStateNotifier, CounterState>(
create: (_) => CounterStateNotifier(),
child: HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('state_notifier sample'),
),
body: Center(
child: Text(
context.select<CounterState, int>((state) => state.count).toString(),
),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => context.read<CounterStateNotifier>().increment(),
label: Text('1'),
icon: Icon(Icons.add),
),
);
}
}
Widgetツリーの上層で、StateNotifierProviderを作成すれば、その子Widgetであればどこでも、CounterStateNotifierを取得することができます。
ChangeNotifierを使った場合、Consumer等を挟まなくてはいけなかったのですが、state_notifierの場合スッキリ書けます。
home: StateNotifierProvider<CounterStateNotifier, CounterState>(
create: (_) => CounterStateNotifier(),
child: HomePage(),
),
CounterStateNotifierを取得したい場合、以下のようにすれば、取得できます。
context.select<CounterState, int>((state) => state.count).toString(),
...
onPressed: () => context.read<CounterStateNotifier>().increment(),
context.readとcontext.selectには、以下の違いがあります。
-
context.read<CounterStateNotifier>()
- State更新時リビルドしない
- StateNotifierのメソッドを使いたいときに使う
-
context.select<CounterState, int>()
- State更新時リビルドする
- 状態が変わったときに画面に反映したい場合に使う
おわりに
「state_notifier」を使用することにより、簡単にWidgetから「状態」と「ロジック」を分離することができました。
「freezed」を使用することにより、Stateをデータを保持するだけのクラス、 変更不可(イミュータブル/freezed)なクラスにすることができました。
コードがきれいでスッキリして、好きです。
超オススメですので、「state_notifier + freezed」を使ってみてはいかがでしょうか。
本記事のコードについて、以下のGitHubリポジトリに公開しています。
https://github.com/karamage/flutter_state_notifier_freezed_sample