Flutter

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

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

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

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

  • hooks_riverpod
  • flutter_hooks
  • flutter_state_notifier
  • freezed

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({this.title});

  @override
  Widget build(BuildContext context) {
    final _counter = useProvider(counterProvider.state).counter;
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // Controllerの処理を呼び出し
        onPressed: () => context.read(counterProvider).increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

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

また、カウントする処理もProviderに移してあり、context.readで呼ぶことができます。

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

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

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

}

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

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

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

final counterProvider =
    StateNotifierProvider.autoDispose((ref) => CounterController(ref.read));

class CounterController extends StateNotifier<CounterState> {
  CounterController(this._reader)
      : super(CounterState(
          // Stateの初期値が決まっている場合、コンストラクタで設定できる
          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にてどうぞ。