LoginSignup
67

More than 3 years have passed since last update.

state_notifier と freezed を使って、Flutterのカウンターアプリをつくるよ

Last updated at Posted at 2020-05-16

はじめに

いま、Flutter界隈では、「state_notifier + freezed」を使って開発するのがアツい! と、評判です。

Flutter state_notifierいい感じなので使ったほうが良いですよ
https://qiita.com/_masaokb/items/fe77495db0aeba226d2a
スクリーンショット 2020-05-18 14.42.57.png

「state_notifier」は、providerと組み合わせて使い、Widgetから「状態」と「ロジック」を簡単に分離し&通知することができるライブラリです。Widgetの状態管理を楽にしてくれたり、無駄なリビルドを抑制したりしてくれます。

「freezed」は、State(状態)を「データを保持するだけのクラス」として変更不可(イミュータブル)なクラスとして表現することができます。

Flutter界隈のエンジニアの方々もオススメされているので、「流行ってるみたいだし、めっちゃよさげだし、私も乗り遅れないようにしないと」と思いました。

私も、実際に使ってみて、めちゃくちゃ良かった ですし、オススメ です。

そこで本記事では、「state_notifier + freezed」について、カウンターアプリを作りながら説明します。

スクリーンショット 2020-05-18 14.32.18.png

本記事のコードについて、以下のGitHubリポジトリに公開しています。
https://github.com/karamage/flutter_state_notifier_freezed_sample

state_notifier について

https://pub.dev/packages/state_notifier
スクリーンショット 2020-05-18 14.42.57.png

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
スクリーンショット 2020-05-18 14.47.04.png

freezedパッケージは、イミュータブルなデータモデルを作成するのに使います。
Stateは、データを保持するだけのクラスにしたいので、 変更不可(イミュータブル/freezed)なクラスにします。
state_notifierでは、State(状態)をイミュータブルなデータモデルとして扱います。
state_notifierとfreezedは相性が良く、セットで使うのが推奨されます。
freezedを使うと、StateにcopyWith(clone)メソッドが自動的に生えるので、便利です。

state_notifier と freezed をパッケージインストール

pubspec.yaml を以下のように書きます。

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」を使用して実装してみます。

Simulator Screen Shot - iPhone 11 Pro - 2020-05-16 at 17.38.36.png

CounterStateとCounterStateNotifierを定義する

新規ファイル「counter_state.dart」 を作成し、以下のコードを記述してください。

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に以下のコードを記述します。

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の場合スッキリ書けます。

main.dart
      home: StateNotifierProvider<CounterStateNotifier, CounterState>(
        create: (_) => CounterStateNotifier(),
        child: HomePage(),
      ),

CounterStateNotifierを取得したい場合、以下のようにすれば、取得できます。

main.dart
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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67