Riverpod 2.x の Provider まとめ

Riverpod 2.x の Provider まとめ

Riverpod のメジャーバージョンが2へと上がり、新機能が追加されて、よりシンプルかつパワフルなコーディングが可能となりました。しかしながら、Riverpod はプロバイダが中心的な役割を果たしますが、それらは種類が多く、適宜使い分けることが依然と難しい状況です。そこでこの記事では、バージョン2におけるプロバイダごとの違いを整理します。

8種類のプロバイダ

RIverpod 2.x では新たに2つのプロバイダが追加され、合計8種類が用意されています。新たに加わった2種類は、従来からある3種類を置き換えることができるため、実質的には5種類のプロバイダを使い分けることになるでしょう。

  1. Provider
  2. StateProvider(レガシー)
  3. StateNotifierProvider(レガシー)
  4. FuturProvider
  5. StreamProvider
  6. ChangeNotifierProvider(レガシー)
  7. NotifierProvider(RIverpod 2.0 で追加)
  8. AsyncNotifierProvider(RIverpod 2.0 で追加)

Provider

Provider は基本のプロバイダです。

特徴

  • 同期的に値を生成する
  • 変更不可能な状態を持つ

用途

  • 同期計算結果のキャッシュ
  • (リポジトリやHTTPクライアントのような)変更されない依存関係の供給

例:リポジトリの供給

import 'package:http/http.dart' as http;
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Weather, OpenWeatherMapAPI のインポートは省略

abstract class WeatherRepository {
  Future<Weather> getWeather({required String city});
}

final weatherRepositoryProvider = Provider<WeatherRepository>((ref) {
  return HttpWeatherRepository(api: OpenWeatherMapAPI(), client: http.Client());
});

WeatherRepository コントラクトを実装した HttpWeatherRepository は、変更可能な状態を持っていないため、Provider で供給するのに適しています。

補足として、このようなケースでの Riverpod 以外の方法の選択肢としては、get_it によるサービスロケータや flutter_bloc ライブラリが挙げられます。

FutureProvider

FutureProvider は非同期操作が可能な Provider です。

特徴

  • 非同期に値を生成
  • AsyncValue による3つの状態——ローディング状態 (loading)、エラー状態 (error)、ロード完了状態 (data) ——ごとに処理を分けることができる

用途

  • 非同期計算結果のキャッシュ
  • 非同期操作の過程や結果に応じた処理

例:非同期で取得される天気データの供給

FutureProvider の定義側:

// 天気データリポジトリプロバイダ
final weatherRepositoryProvider = Provider<WeatherRepository>((ref) {
  return WeatherRepository();
});

final weatherFutureProvider = FutureProvider.autoDispose<Weather>((ref) {
  // プロバイダからリポジトリを取得
  final weatherRepository = ref.watch(weatherRepositoryProvider);
  // Future<Weather> を返すメソッドを呼び出す
  return weatherRepository.getWeather(city: 'London');
});

FutureProvider を利用するウィジェット内部:

Widget build(BuildContext context, WidgetRef ref) {
  // FutureProvider を監視し、 AsyncValue<Weather> を取得する
  final weatherAsync = ref.watch(weatherFutureProvider);
  // パターン マッチングを使用して状態を UI にマッピングする
  return weatherAsync.when(
    loading: () => const CircularProgressIndicator(),
    error: (err, stack) => Text('Error: $err'),
    data: (weather) => Text(weather.toString()),
  );
}

StreamProvider

StreamProviderFutureProvider のストリーム版です。

特徴

  • Riverpod が提供する AsyncValue によって loading/error 状態を処理することができる
  • ストリームの監視を ref.watch で行える
  • ストリームの直近の値をキャッシュする

用途

  • (Firebase や WebSocket といった)Stream 形式のデータ供給の監視
  • 一定時間ごとの別のプロバイダの更新
  • テスト時のストリームをモックする

例:Firebase Authentication APIを監視してリアクティブにUIを構築する

StreamProvider の定義側:

final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
  // FirebaseAuth を取得する
  final firebaseAuth = ref.watch(firebaseAuthProvider);
  // Stream<User?> を返すメソッドを呼び出す
  return firebaseAuth.authStateChanges();
});

// FirebaseAuth インスタンスにアクセスするプロバイダ
final firebaseAuthProvider = Provider<FirebaseAuth>((ref) {
  return FirebaseAuth.instance;
});

StreamProvider を利用するウィジェット内部:

Widget build(BuildContext context, WidgetRef ref) {
  // StreamProvider を監視して、 AsyncValue<User?> を取得する
  final authStateAsync = ref.watch(authStateChangesProvider);
  // パターン マッチングを使用して状態を UI にマッピングする
  return authStateAsync.when(
    data: (user) => user != null ? HomePage() : SignInPage(),
    loading: () => const CircularProgressIndicator(),
    error: (err, stack) => Text('Error: $err'),
  );
}

NotifierProvider

NotifierProvider は時間の経過とともに変化する可能性のある状態を公開する Notifier のプロバイダです。

特徴

  • ref を渡すことなく読み取りが可能
  • 複雑な同期的な初期化が可能
  • StateProvider を置き換えることができる

用途

  • 同期的な StateProviderStateNotifierProvider のよりスマートな代替え手段

例:カウンター

NotifierProvider の定義側

import 'package:flutter_riverpod/flutter_riverpod.dart';

class Counter extends Notifier<int> {
  @override
  int build() {
    return 0;
  }

  void increment() {
    state++;
  }
}

final counterProvider = NotifierProvider<Counter, int>(() {
  return Counter();
});

NotifierProvider を利用する側

class CounterWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 1. プロバイダーを監視し、値が変更されたら再構築する
    final counter = ref.watch(counterProvider);
    return ElevatedButton(
      // 2. 値を使用する
      child: Text('Value: $counter'),
      // 3. ボタンコールバック内で状態を変更する
      onPressed: () => ref.read(counterProvider.notifier).increment(),
      // → StateProvider 風に書くなら、
      //   ref.read(counterProvider.notifier).state++ としてもよい
    );
  }
}

AsyncNotifierProvider

AsyncNotifierProvider は非同期で初期化される NotifierProvider で、AsyncNotifier のプロバイダです。但し、autoDispose を使用する場合は、AsyncNotifier の替わりに AutoDisposeAsyncNotifier を利用します。

特徴

  • ref を渡すことなく読み取りが可能
  • 複雑な非同期的な初期化が可能
  • state プロパティは AsyncValue でラップされた形式となる

用途

  • 非同期的な StateProviderStateNotifierProvider のよりスマートな代替え手段

例:認証

AsyncNotifierProvider の定義側:

import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// AsyncNotifier を継承する
class AuthController extends AsyncNotifier<void> {
  // build メソッドをオーバーライドして FutureOr を返す
  @override
  FutureOr<void> build() {
    // 値を返すか、void の場合は何もしない
  }

  Future<void> signInAnonymously() async {
    // 自ら ref を読み取ることができる
    final authRepository = ref.read(authRepositoryProvider);
    // ロード中
    state = const AsyncLoading();
    // ロード完了
    state = await AsyncValue.guard(authRepository.signInAnonymously);
  }
}

final authControllerProvider = AsyncNotifierProvider<AuthController, void>(() {
  return AuthController();
});

AsyncNotifierProvider を利用する側:

ref.read(authControllerProvider.notifier).signInAnonymously()