Open4

[Flutterr] Riverpod

gonCgonC

Riverpodとは

  • Riverpod公式サイトでは「RiverpodはFlutter/Dartのリアクティブキャッシュフレームワークである」と紹介していて、これはRiverpodの状態管理パッケージとしての側面を表している
  • 状態変化に反応してウィジェットを更新する仕組みが提供されており、その状態を保持するという意味
  • 状態の保持に関してはウィジェットのライフサイクルに左右されず、破棄のタイミングをコントロールすることができる
  • 状態管理パッケージとしての側面以外にも、クラスの依存関係を解決するための仕組みを提供している(DI)
gonCgonC

Riverpodの主要なクラス

主要なクラス

1. Provider

2. RefとWidgetRef

3. ConsumerWidget

それぞれの役割と関係性

  • Providerが状態をキャッシュし、RefやWidgetRefを介して状態を提供する
  • ConsumerWidgetはウィジェットのサブクラスである
  • ConsumerWidgetのbuildメソッドの引数にはWidgetRefが渡され、それを介して状態を取得し、ウィジェットを構築する
  • Providerの持つ状態が変化すると、buildメソッドが再度呼び出される
gonCgonC

実装サンプル

状態を保持するNotifierと提供するNotifierProviderの実装

int型の数値を状態として提供し、かつその値を変更可能なProviderを実装する

class CounterNotifier extends Notifier<int>{
  
  int build() => 0;

  void increment(){
    state = state + 1;
  }
}

final counterNotifierProvider = NotifierProvider<CounterNotifier, int>((){
  return CounterNotifier();
});
  • 状態を変更可能なProviderはNotifierというクラスを使う
  • カウンタの値を保持し、インクリメントするCounterNotifierというクラス
  • Notifierクラスは自分の状態をstateプロパティに保持している
  • Notifier<int>と渡している型パラメーターが状態の型になる
  • CounterNotifierクラスをNotifierProviderで提供する

表示するためのWidgetの実装

class MyHomePage extends ConsumerWidget{
  const MyHomePage({super.key,required this.title});

  final String title;

  
  Widget build(BuildContext context, WidgetRef ref){
    final counter = ref.watch(counterNotifierProvider);
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
            'You have pushed the button this many times:',
            ),
            Text(
              '$counter',
              style: Theme.of(context).textTheme.headlineMedium,
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
          onPressed: (){
            ref.read(counterNotifierProvider.notifier).increment();
          },
      tooltip: 'Increment',
      child: const Icon(Icons.add),
      )
    );
  }
}
  • WidgetRefのwatchメソッドを使って状態を監視した場合、状態が変化するとbuildメソッドが再び呼び出される

Riverpodの関連パッケージ

Riverpodは以下の3つのパッケージで構成されている

基本機能を提供するパッケージ

  • riverpod
  • flutter_riverpod(flutterでは通常これを使う)
  • hooks_riverpod

Providerのコードを生成するパッケージ

  • riverpod_generator(コード生成)
  • riverpod_annotation(コード生成のためのアノテーション)
    ※ build_runnerパッケージも必要になる
// コード生成を利用しない場合
final greetProvider = Provider((ref){
    return 'Hello, Flutter!';
});

// コード生成を利用する場合

String greet(GreetRef ref){
    return 'Hello Flutter!';
}
  • Providerに関するコードの記述する際の意思決定が減る
  • Providerへ渡すパラメータの制限がなくなる
  • Providerの変更がホットリロードできる

静的解析を行うパッケージ

  • riverpod_lint
    ※ custom_lintも必要

関連パッケージまとめ

pubspec.yaml
dependencies:
    flutter_riverpod:
    riverpod_annotation:

dev_dependencies:
    riverpod_generator:
    build_runner:
    custom_lint:
    riverpod_lint:

コマンドラインから導入する場合

$ flutter pub add flutter_riverpod riverpod_annotation
$ flutter pub add --dev riverpod_generator build_runner custom_lint riverpod_lint

コードの生成例

part 'main.g.dart';

class CounterNotifier extends _$ConterNotifier{
  
    int build() => 0;
  
  void increment(){
    state = state + 1;
  }
}
  • @riverpodアノテーションを付与する
  • _$ + クラス名の型を継承する
  • 初期値をbuildlメソッドで返す

実装が完了したらコード生成のために、以下のコマンドを実行

$ flutter packages pub run build_runner build
gonCgonC

ProviderScope

  • プロバイダが持つ状態(state)やロジック(logic)をアプリ全体でアクセス可能にするもの
  • アプリケーションのルートにプロバイダのスコープを設定し、そのスコープ内で定義された全てのプロバイダの状態を管理することができ、これにより、状態の共有や更新がアプリケーションのどの部分からでも行えるようになる

ProviderScopeの使い方

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