🙆‍♀️

【Flutter】 Riverpod チートシート(書きかけ)

2021/11/03に公開

背景

状態管理のプラグインとして、Riverpodを使ってます。そこそこ使えるようになりましたが、(年のせいか、元々記憶力がないだけなのか)細かいところが思い出せない。そのため、今回Riverpod関連が一覧で見られるチートシートを作ろうと思いました。テスト用の実装として、FlutterのテンプレートのカウントアップアプリをRiverpodの各Providerで実装しました。簡単ではありますが、参考になると思います。各Providerの最後の「ソース」から飛べます。
あくまで解っている人向けのチートシートなので、もうちょっと細かい説明を「本」として出したいと思っております。

実施環境

Riverpodで破壊的な変更が(人知れず)起こっているので、見つけたらご連絡ください。一応、気をつけてはいるのです、、、

(追記) 2021/11/05、1.0.0がリリースされました!それに合わせて、修正しました。

flutter_riverpod: ^1.0.0

[√] Flutter (Channel stable, 2.5.3, on Microsoft Windows [Version 10.0.19043.1288], locale ja-JP)
[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[√] Android Studio (version 2020.3)

前準備

flutter pub add flutter_riverpod

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

void main() {
-  runApp(const MyApp());
+  runApp(ProviderScope(child: const MyApp()));
}

Provider

Provider 定数

  1. 定義
Provider<String> _provider =
    Provider((ref) => 'You have pushed the button this many times:');
Provider<String> _provider =
    Provider((ref) => ref.watch(anotherProider).state * 2);

  1. 表示
ref.watch(_provider)
  1. 変更
    できない

  2. 使用用途
    定数に対して使う。ほかにも、他のProviderの値を受けて、値を表示することもできる。

ソース

StateProvider 変数

  1. 定義
StateProvider<int> _stateProvider = StateProvider((ref) => 0);
  1. 表示
ref.watch(_stateProvider)
  1. 変更
    どれを使うべきなのだろうか、、
ref.read(_stateProvider.state).state++;
ref.read(_stateProvider.state).state = ref.read(_stateProvider) + 1;
ref.read(_stateProvider.state).update((state) => state + 1);

ref.read(_stateProvider.notifier).state++;
ref.read(_stateProvider.notifier).state = ref.read(_stateProvider) + 1;
ref.read(_stateProvider.notifier).update((state) => state + 1);
  1. 使用用途
    riverpodのProviderの中で基本。intやクラスなどの値を管理する。

ソース

StateNotifier 変数 + メソッド

  1. 定義
class CounterNotifier extends StateNotifier<int> {
  CounterNotifier() : super(0);

  void countUp() {
    state++;
  }
}
final _provider =
    StateNotifierProvider<CounterNotifier, int>((ref) => CounterNotifier());
  1. 表示
Text(
  '${ref.watch(_provider)}',
  style: Theme.of(context).textTheme.headline4,
),
  1. 変更
ref.watch(_provider.notifier).countUp(),
  1. 使用用途
    メソッドがあるので、MVVMモデルっぽく使う方がいる。(個人的には、VMは一つのクラスとして独立させた方が良いと考えている)

ソース

ChangeNotifierProvider

  1. 定義
class Counter extends ChangeNotifier {
  int _counter = 0;
  get counter => _counter;

  void countUp() {
    _counter++;
    notifyListeners();
  }
}

final _provider = ChangeNotifierProvider((ref) => Counter());
  1. 表示
ref.watch(_provider).counter
  1. 変更
ref.watch(_provider).countUp()
  1. 使用用途
    ChangeNotifierが好きな人用?個人的には、notifyListeners()を書き忘れるので、苦手だったりします。

ソース

FutureProvier Futureを使う

  1. 定義
final _provider = FutureProvider<int>((ref) async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getInt(KEY) ?? 0;
});
  1. 表示
    Widgetを返す
ref.watch(_provider).when(
      data: (data) => Text(
        '${ref.watch(_provider).value}',
        style: Theme.of(context).textTheme.headline4,
      ),
      loading: () => const CircularProgressIndicator(),
      error: (error, stack) => Text('error'),
),
  1. 変更
onPressed: () async {
  final prefs = await SharedPreferences.getInstance();
  int currentValue = prefs.getInt(KEY) ?? 0;
  prefs.setInt(KEY, currentValue + 1);
  ref.refresh(_provider);
},
  1. 使用用途
    Future型を使用するので、SharedPreferencesだけでなく、WebAPIやファイル読込など使用するケースは多いと思われる。

ソース

StreamProvider Streamを使う

  1. 定義
StreamController<int> streamController = StreamController();

final _provider = StreamProvider<int>((ref) {
  if (streamController.hasListener) {
    streamController = StreamController();
  }
  return streamController.stream;
});
  1. 表示
    Widgetを返す
ref.watch(_provider).when(
data: (data) => Text(
'${ref.watch(_provider).value}',
style: Theme.of(context).textTheme.headline4,
),
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text('${error}'),
),
  1. 変更
onPressed: () { 
  count++; 
  streamController.sink.add(count); 
},
  1. 使用用途
    Firestoreでsnapshotsを使用するケースが一番使えると思う。

ソース

表示するためのWidget

Consumer

通常のWidgetをラップし(この場合Text)、ラップしたWidget内でProviderでアクセスできるようになる。StatelessWidgetやStatefulWidgetのWidgetでriverpodを使用したい場合に使用する。

    Consumer( 
      builder: (context, ref, child) => Text( 
	'${ref.watch(_stateProvider).state}', 
	style: Theme.of(context).textTheme.headline4, 
      ), 
    )

ソース

ConsumerWidget

StatelessWidgetのriverpod版。全面的にriverpodを使っていくので、WidgetごとにConsumerで囲むのが厳しい場合に使用する。もしくは、riverpod専用のWidgetを作成する場合に使用する。

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

  final String title;

  
  Widget build(BuildContext context, WidgetRef ref) {
  	return Text(ref.watch(_provider));
  }
}

ソース

プロバイダ修飾子

autoDispose

参照されなくなったプロバイダのステートを自動で破棄する場合に使用する。

final _provider = FutureProvider.autoDispose<int>((ref) async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.getInt(KEY) ?? 0;
});

基本読み込まれたデータはそのまま使用される。しかし、refreshを使用することで、強制的にデータを再度読み込むことができる。

ref.refresh(_setMenuListProvider);

family

たとえばデータベースから、該当のIDのTODOのデータを取得するとき、IDをAPIの引数として渡します。引数を使用する場合は、familyを使用する。
以下、String?型を引数として、Todo型のデータを返します。(IDがない場合は空のTODOを、ある場合はデータベースから取得します)

定義

final _todoProvider =
    FutureProvider.autoDispose.family<Todo, String?>((ref, id) async {
  Todo todo = Todo(id: '', name: '');

  if (id != null && id.isNotEmpty) {
    var database = GetIt.I.get<Database>();
    todo = await Todo.selectOne(database, id);
  }

  return todo;
});

利用

ref.watch(_todoProvider(id)).when(
    error: (error, _) => Text(error.toString()),
    loading: () => const EmProgressIndicator(),
    data: (todo) => Text('${todo.id} ${}todo.name');
);

気に入っていただけましたら

Twitterで呟いていただいたり、ブログ記事等からリンクを貼っていただけると、非常に嬉しいです!また、間違いのご指摘や使用用途のアイデアがあれば、コメントをお願いします!

より詳しく学習するために

チートシートを前提としているため、カウントアップで状態管理の対象をintとさせていただきました。データクラスを対象としたい、WebAPIやFirestoreから取得したデータをRiverpodで扱いたい、という方がいると思います。その場合は、以下のUdemy講座をご利用ください。Riverpodの基本から、MVVMモデルへの応用、もちろん、WebAPIやFirestoreからのデータも取り扱っています。ぜひご受講ください。
クーポンコードもありますので、ご利用ください。

Flutter x Riverpod x MVVMで実現するシンプルな設計

https://zenn.dev/sakusin/articles/a00aa8af321f54

Discussion