Flutter

Flutter Riverpodを使用したカウンターアプリのサンプル

最近はFlutterの状態管理にRiverpodを使用しています。

Flutterでプロジェクトを作成したときに生成されるカウンターアプリは、状態管理にStatefulWidgetを使用していますが、Riverpodを使用したバージョンを備忘録として残しておきます。

本記事で使用している構成は、以下の組み合わせです。

  • hooks_riverpod : ^0.14.0+4
  • flutter_hooks : ^0.17.0
  • flutter_state_notifier : ^0.7.0
  • freezed : ^0.14.2

※2021/7/18 versionを更新

Riverpodで状態管理する場合に、使用するパッケージ

Riverpodを使用して状態管理する場合、おおすじは似たような感じになりますが、使用するパッケージが人によりちょこちょこ異なります。

私が使用するパッケージ構成は以下のとおりです。

  • hooks_riverpod
  • flutter_hooks
  • flutter_state_notifier
  • freezed

いずれも、pub.devにあがっているので、pubspec.yamlに追加すればインストールすることができます。

ちなみにRiverpod作者のRemi Rousseletさんも上記のような構成が好きなようです。

Riverpodを使用した、カウンターアプリのサンプル

私は個人的にMVVMの構成を使うことが多いので、今回のサンプルもMVVMで構成しています。(ただし、今回Modelは使用していません。)

ファイル構成は以下にしています。

ポイントをかいつまんで説明します。

main()でProviderScopeを使用する

main()でProviderScopeを使用します。

void main() {
  runApp(ProviderScope(
    child: MyApp(),
  ));
}

MyAppでProviderを使用できるようになります。

viewはHookWidgetを継承する

viewではProviderを使用するために、HookWidgetを継承します。

class MyHomePage extends HookWidget {
  final String title;

  MyHomePage({required this.title});

  @override
  Widget build(BuildContext context) {
    // counterProviderの状態
    final counter = useProvider(counterProvider).counter;
    // CounterControllerを使用するために読み取り
    final notifier = useProvider(counterProvider.notifier);
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // CounterControllerに置いてある処理を呼ぶ
        onPressed: () => notifier.increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

HookWidgetを継承することで、useProviderを使用してProviderのstateにアクセスできるようになります。

また、カウントする処理もProviderに移してあり、こちらはuseProviderの引数にprovider.notifierで読み取ります。

StateクラスにProviderで管理する状態を定義する

Stateクラスで管理したい状態を定義する。

@freezed
class CounterState with _$CounterState {
  const CounterState._();
  const factory CounterState({
    required int counter,
  }) = _CounterState;
}

カウンターアプリではcounterしかないため1つですが、複数ある場合は複数定義します。
freezedパッケージによりimmutableなクラスとなっています。

freezedパッケージの解説は以下の記事をご覧ください。

【簡単】Flutter freezedパッケージを最小限の機能だけで使用する手順Flutterの状態管理手法は現在時点(2021年2月時点)で、以下のような組み合わせが流行っています。 Riverpod + S...

ControllerクラスでグローバルなProviderを定義する

ControllerクラスにてStateNotifierを継承したControllerと、グローバルなProviderを用意します。

final counterProvider = StateNotifierProvider<CounterController, CounterState>(
    (ref) => CounterController(ref.read));

class CounterController extends StateNotifier {
  CounterController(this._reader)
      : super(CounterState(
          // Stateを初期化する。counterは必須にしているため、初期値設定しないとエラー
          counter: 0,
        )) {
    // DBからのデータ初期取得処理などあれば、取得処理を書く
  }

  // 他のproviderを使用する場面などで使用する
  final Reader _reader;

  // increment処理をControllerに定義
  void increment() {
    state = state.copyWith(
      counter: state.counter + 1,
    );
  }
}

カウンターの初期値と、カウンターを1増やす処理を用意しています。

CounterStateで定義した状態を操作する場合、stateの値を更新します。

freezedパッケージでCounterStateはimmutableになっているので、stateを更新する場合はfreezedパッケージで生成されたcopyWithを使用します。

まとめ

Riverpodを使用したカウンターアプリのサンプルでした。

人それぞれ書き方は色々あると思いますが、個人的にはこれが1番好きです。Riverpodを使う前に使っていた、ProviderのときにMVVM構成にしていたので、近い感じで使用できていい感じです。

今回のサンプルの全容を確認したい場合は、Githubにてどうぞ。