Riverpod 3 入門 一番簡単な説明
Flutterで状態管理ツールといえばriverpodというパッケージがとても有名です。
簡単な使い方を説明します。
Riverpod 3 を少し使ってみましょう。
Riverpodとは
A Reactive Caching and Data-binding Framework
リアクティブ・キャッシングとデータバインディング・フレームワーク
状態管理ツールです。私はそう受け止めています。
- グローバルから値を提供できます。
- 深いWidgetでも提供された値を取得できます。
- 提供されている値が変わるとWidgetが再描画されます。
- Widgetから変更することも可能です。
使いたくなる場面
私の場合ですけど。
- sqfliteを使用することになりました。
- DBインスタンスは1つでいいし、グローバル的にアクセスできればいいかなと考えました。
- そこでシングトンパターンを採用しました。
- じゃあ、どこで初期化しようかなと考えます。
- するとまぁやっぱり、App起動の最初のmain()関数かなとなります。そういう例もよく見ますね。
- さて、初期化したdatabaseインスタンスを子孫Widgetに渡したいですよね。
- 中継するすべてのWidgetのコンストラクタの引数にそれを追加しなければならなくなります。
- そーです。引数のリレーが始まるのです。
- これはとても面倒です。関係ないWidgetを修正したくありません。グローバルから飛び越えるように渡したいなと思ってしまいます。
- Riverpod登場
環境
- Riverpod 3.1.0
- Flutter 3.38.3
- Mac mini 2024 Apple M4
Riverpod 3 注意事項
riverpodには3種類のパッケージがあります。flutterで使用する場合は、「flutter_riverpod」を選びます。
- riverpod
- flutter_riverpod <- これです
- hooks_riverpod
また、providerという名称のパッケージもあります。
riverpodにはproviderという名称の概念があります。providerパッケージとも勘違いしないように注意しましょう。
Riverpod 3 のProviderは6種類
提供できる値オブジェクトの種類に応じて、providerが6種類あります。
| Synchronous | Future | Stream | |
|---|---|---|---|
| Unmodifiable | Provider | FutureProvider | StreamProvider |
| Modifiable | NotifierProvider | AsyncNotifierProvider | StreamNotifierProvider |
Notifier系は値を変更できます。(実際は新しいオブジェクトを再割り当てします。)
Legacyになったproviderもある
古くなったproviderもあります。
- StateProvider
- StateNotifierProvider
- ChangeNotifierProviderです。
これら使用するときは新たにimportが必要になりました。「import ‘package:riverpod/legacy.dart’」です。
Providerを使ってみる
一番簡単。
同期的、変更メソッドなしのproviderです。
用途:値オブジェクトの提供です。遠くのWidgetまで届きます。
今回は単純な文字列を提供してみましょう。
Step概要
- Install riverpod
- Install riverpod lint
- Import 追加
- runAppにProviderScopeを挟む
- 提供用Provider準備
- ConsumerWidgetを継承して受信用Widgetを作成
- ref.watchを利用して値オブジェクトを取得する。
Install
pubspec.yamlに以下を追加します。
dependencies:
flutter_riverpod: ^3.1.0コマンドラインで以下のように叩いてもよし。
flutter pub add flutter_riverpodlint有効化
analysis_options.yamlに以下を追加します。
analyzer:
plugins:
- riverpod_lint
plugins:
riverpod_lint: ^3.1.0VSCode拡張機能「Flutter Riverpod Snippets」
こちらも支援を受けられます。
Import
import 'package:flutter_riverpod/flutter_riverpod.dart';ProviderScope設置
ここは補完が効くはず。
void main() {
runApp(ProviderScope(child: MyApp()));
}Provider
Topレベルに記述します。main()と同じレベルです。
final helloWorldProvider = Provider((_) => 'Hello');中身は、「(_) => ‘Hello’」ですね。
文字列「Hello」です。これが値として提供されます。
ConsumerWidget
StatelessWidgetの代わりに、ConsumerWidgetを継承します。
build()メソッドの引数に「ref」が追加されています。
// ConsumerWidgetを継承します
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
//値取得
final String value = ref.watch(helloWorldProvider);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Example')),
body: Center(
child: Column(
children: [
// 値表示
Text(value),
],
),
),
),
);
}
}
ref.watchを利用して値オブジェクトを取得する
refを経由して、providerから値を取得できます。
final String value = ref.watch(helloWorldProvider);取得後は適当にWidgetで使用します。
// 使用例
Text(value),Code
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
// provider
final helloWorldProvider = Provider((_) => 'Hello');
// ProviderScope
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
home: const MyHome(),
);
}
}
// ConsumerWidgetを継承します
class MyHome extends ConsumerWidget {
const MyHome({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// 値取得
// providerを指定します
final String value = ref.watch(helloWorldProvider);
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Example')),
body: Center(
child: Column(
children: [
// 値表示
Text(value),
],
),
),
),
);
}
}
結果
providerから適当なwidgetへ値を提供できました。
Notifierを使う
さて、少し慣れてきましたね。
他のProviderも使ってみましょう。
更新機能のあるNotifierProviderを使ってみましょう。
Notifier
Notifierには、状態を表す”state”があります。これを操作するとwatchしているwidgetが再描画されます。
まずはコードから。「counter_provider.dart」を以下のように作成してみました。
import 'package:flutter_riverpod/flutter_riverpod.dart';
/// Notifier クラスの定義
class CounterNotifier extends Notifier<int> {
/// これは初期値を返します。
/// 型はジェネリクスTとも一致します。
/// watch、readが初めて実行されたときにbuildが実行されるようです。lazyですね。
@override
int build() {
return 0; // 初期値 // state が 0
}
void increment() {
// stateへの操作で状態が更新されていく
state++;
}
void decrement() {
state--;
}
void reset() {
state = 0;
}
}
/// NotifierProvider の定義
/// Notifierを指定します。
final counterProvider = NotifierProvider<CounterNotifier, int>(
//() => CounterNotifier(), // これでも良いがnewを使う方が一般的のようです。
CounterNotifier.new,
);
無印なProviderとは違いが色々ありますね。
- まず、Notifierクラスを継承したクラスを定義します。
- 提供する値の型をジェネリクスで指定します。
- stateの初期値はbuild()メソッドで初期化します。
- stateを操作するメソッドを追加できます。
- 今回は、このInt型の値を操作する、追加、削除、リセットのメソッドを追加しました。
Notifierができたら、NotifierProviderに登録します。
final counterProvider = NotifierProvider<CounterNotifier, int>(
CounterNotifier.new,
);Consumer
次は、Providerから値を受け取る側を準備します。
今回はNotifierですので、更新処理もできるようにします。
ConsumerWidgetを継承したクラスを作成します。「counter_widget.dart」を次のように作成してみました。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_provider.dart';
// ConsumerWidgetを使ってProviderの値を監視する例
// 表示と更新を司るクラス
class CounterWidget extends ConsumerWidget {
const CounterWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
// watchでProviderの値を監視する
final count = ref.watch(counterProvider);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 表示ですね
Text('Count: $count'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
icon: const Icon(Icons.remove),
// readを使いnotifier経由でメソッドを叩く
onPressed: () => ref.read(counterProvider.notifier).decrement(),
),
IconButton(
icon: const Icon(Icons.add),
onPressed: () => ref.read(counterProvider.notifier).increment(),
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () => ref.read(counterProvider.notifier).reset(),
),
],
),
],
);
}
}
- watchで状態を監視し値を取得します。状態に変更があればbuildが再実行されます。
- 監視ではなく一回だけ取得するような場合はreadを使用します。notifierを指定し、メソッドを実行します。
main
mainはこんな感じにしてみました。
flutterのCounterアプリから余計なコードを落として、Riverpod的な部分を足しています。
StatefulWidgetが無くなっていますね。
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'counter_widget.dart';
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Riverpod Demo',
theme: ThemeData(colorScheme: .fromSeed(seedColor: Colors.deepPurple)),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Riverpod Counter Example')),
body: Center(child: const CounterWidget()),
);
}
}

結果
状態の監視、値の取得と更新、状態変化に反応して再描画が実装できましたね。
おわり