Riverpodの使い方まとめとく
前準備
pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_riverpod: ^2.1.1
main.dart
void main() {
runApp(const ProviderScope(
child: MyApp()
));
}
以下はプロバイダーとは何かについてドキュメントの引用。プロバイダーの値を変更、監視することで状態管理する
プロバイダとは、状態の一部をカプセル化し、その状態をリッスンできるようにするオブジェクトのことである。
ConsumerとWidget
プロバイダーにアクセスするために必要なWidgetRef
インスタンスを取得するためにComsumer
ConsumerWidget
ConsumerStatefulWidget
を使用する
Comsumer
任意の場所でプロバイダーを使いたい場合に使用
final fooProvider = Provider((ref) => "foo");
class FooWidget extends StatelessWidget {
const FooWidget({super.key});
Widget build(BuildContext context) => Consumer(
builder: (context, WidgetRef ref, child) => Text(
ref.watch(fooProvider),
),
);
}
ConsumerWidget
StatelessWidget
なWidget
でプロバイダーを使いたい場合に使用。Consumer
でラップせずに使うとWidget
全体が再ビルドの対象になる点に注意する
final fooProvider = Provider((ref) => "foo");
class FooWidget extends ConsumerWidget {
const FooWidget({super.key});
Widget build(context, WidgetRef ref) => Text(
ref.watch(fooProvider),
);
}
ConsumerStatefulWidget
StatefulWidget
なWidget
でプロバイダーを使いたい場合に使用。Consumer
でラップせずに使うとWidget
全体が再ビルドの対象になる点に注意する。また、initState
などのライフサイクルメソッド内でプロバイダーの値にアクセスできる
final fooProvider = Provider((ref) => "foo");
class FooWidget extends ConsumerStatefulWidget {
const FooWidget({Key? key}) : super(key: key);
FooWidgetState createState() => FooWidgetState();
}
class FooWidgetState extends ConsumerState<FooWidget> {
void initState() {
super.initState();
ref.read(fooProvider); // foo
}
Widget build(BuildContext context) => Text(
ref.watch(fooProvider),
)
}
プロバイダーへのアクセス
ref.watch
値の変化を監視する。値が変化したらWidget
が再ビルドされる
Consumer(
builder: (context, WidgetRef ref, child) => Text(
ref.watch(fooProvider),
),
);
ref.read
値の変化を監視しない
Consumer(
builder: (context, ref, child) => TextButton(
onPressed: () => ref.read(fooProvider.notifier).state++,
),
);
ref.listen
値の変化を監視する。値が変化してもWidget
の再ビルドはされない
Consumer(
builder: (context, WidgetRef ref, child) {
ref.listen(fooProvider, (previous, next) {
// fooProviderの値が変化すると呼ばれる
});
・・・
},
);
select
再ビルドの条件を制御する。以下の例ではFoo.bar
のみを監視する。Foo.baz
の値が変わっても再ビルドは行われない
class Foo {
final int bar, baz;
const Foo(this.bar, this.baz);
}
final fooProvider = StateProvider<Foo>((ref) => const Foo(0, 0));
・・・
Consumer(builder: (context, ref, child) {
var foo = ref.watch(fooProvider.select((foo) => foo.bar));
return Text("$foo");
}),
プロバイダーから他のプロバイダーへのアクセス
barProvider
はfooProvider
を監視、FooWidget
はbarProvider
を監視。fooProvider
の値が変わるとWidget
が再ビルドされる
final fooProvider = StateProvider((ref) => 0);
final barProvider = StateProvider((ref) => ref.watch(fooProvider) * 2);
・・・
class FooWidget extends StatelessWidget {
Widget build(BuildContext context) => Consumer(
builder: (context, ref, child) => Text(ref.watch(barProvider)),
);
}
プロバイダーの種類
Provider
プロバイダーの利用者に値の操作はさせず、参照のみをさせたい場合に使用する
final fooProvider = Provider((ref) => "foo");
class FooWidget extends StatelessWidget {
Widget build(BuildContext context) => Consumer(
builder: (context, ref, child) => Text(ref.watch(fooProvider)),
);
}
StateProvider
プロバイダーの利用者に値の操作と参照をさせたい場合に使用する。また、値の操作方法を利用者で決めることができる
final fooProvider = StateProvider((ref) => 0);
class FooWidget extends StatelessWidget {
Widget build(BuildContext context) {
・・・
Consumer(
builder: (context, ref, child) => "${Text(ref.watch(fooProvider))}",
),
・・・
Consumer(
builder: (context, ref, child) => TextButton(
onPressed: () => ref.read(fooProvider.notifier).state++,
・・・
),
),
}
}
StateNotifierProvider
プロバイダーの利用者に値の操作と参照をさせたい場合に使用する。ただし、値の操作方法は提供者が公開しているメソッドを使用する
class Foo {
final int value;
const Foo(this.value);
}
class FooController extends StateNotifier<Foo> {
FooController() : super(const Foo(0));
void increment() {
state = Foo(state.value + 1);
}
}
final fooProvider = StateNotifierProvider<FooController, Foo>((ref) => FooController());
class FooWidget extends StatelessWidget {
Widget build(BuildContext context) {
・・・
Consumer(
builder: (context, ref, child) => Text(
"${ref.watch(fooProvider).value}",
),
),
・・・
Consumer(
builder: (context, ref, child) => TextButton(
onPressed: () => ref.read(fooProvider.notifier).increment();,
・・・
),
),
}
}
また、StateNotifier
で管理する状態はイミュータブルな前提のため、以下のようなコードはref.watch
で監視しても変更を検知できない
class Foo {
int value;
Foo(this.value);
}
class FooController extends StateNotifier<Foo> {
・・・
void increment() => state.value++;
}
以下はref.watch
の説明の翻訳を引用
プロバイダが公開する値を返し、その値が変更されたときにウィジェットを再構築します。値が変更されたらウィジェットを再構築する。
上の例ではFoo.value
の値を直接いじってもオブジェクトの参照先が変わらないので変更されたことにならない
ChangeNotifierProvider
プロバイダーの利用者に値の操作と参照をさせたい場合に使用する。ただし、値の操作方法は提供者が公開しているメソッドを使用する。StateNotifierProvider
のミュータブルな値を管理したい版
class Foo {
int value;
Foo(this.value);
}
class FooController extends ChangeNotifier {
Foo foo;
FooController() : foo = Foo(0);
void increment() {
foo.value++;
notifyListeners();
}
}
final fooProvider = ChangeNotifierProvider((ref) => FooController());
notifyListeners()
を呼ぶことで変更を通知する
FutureProvider
プロバイダーの利用者に値の操作はさせず、参照のみをさせたい場合に使用する。Provider
の非同期操作が可能なことと、loading/error
ステータスが簡単に扱える
final fooProvicer = FutureProvider<String>((ref) async {
await Future.delayed(const Duration(seconds: 3));
return "foo";
});
class FooWidget extends StatelessWidget {
Widget build(BuildContext context) => Consumer(
builder: (context, ref, child) => ref.watch(fooProvicer).when(
data: (foo) => Text(foo),
error: (err, st) => const Text("Error"),
loading: () => const Text("Loading"),
),
);
}
また、以下はドキュメントの翻訳の引用
FutureProviderはユーザのインタラクションの後に計算を直接修正する方法を提供しません。これは、単純なユースケースを解決するために設計されている。より高度なシナリオの場合は、StateNotifierProviderの使用を検討してください。
StreamProvider
プロバイダーの利用者に値の操作はさせず、参照のみをさせたい場合に使用する。FutureProvider
のStream
版
final fooProvider = StreamProvider((ref) {
final controller = StreamController();
controller.add("hello");
return controller.stream;
});
class FooWidget extends StatelessWidget {
・・・
}
familyとautoDispose
family
プロバイダーに引数を渡すことができる
final fooProvider = StateProvider<int>((ref) => 0);
final barProvider = StateProvider.family<int, int>((ref, v)
=> ref.watch(fooProvider) * v);
・・・
Consumer(
builder: (context, ref, child) => Text("${ref.watch(barProvider(100))}"),
),
autoDispose
使われなくなったプロバイダーを自動で削除する。また、onDispose
で削除時のコールバックを指定できる。例えばHTTPリクエストが完了する前にプロバイダーが削除される前にリクエストをキャンセルすることができる
final fooProvider = FutureProvider.autoDispose<Foo>((ref) async {
// HTTPリクエストをキャンセルするのに使うトークン
final token = CancelToken();
// プロバイダーが削除されたら呼ばれるコールバックでHTTPリクエストをキャンセル
ref.onDispose(() token.cancel());
var res = await Dio().get('https://・・・', cancelToken: token);
return Foo.fromResponse(res);
});
その他
overrides
プロバイダーを上書き
final fooProvider = StateProvider<int>((ref) => 0);
ProviderScope(
overrides: [
fooProvider.overrideWith((ref) => 100),
],
child: Consumer(
builder: (context, ref, child) => Text("${ref.watch(fooProvider)}"),
),
),
プロバイダーに別名をつける
final fooProvider = StateProvider.family<int, int>((ref, value) => value);
final barProvider = StateProvider<int>((ref) => throw UnimplementedError());
ProviderScope(
overrides: [
barProvider.overrideWith((ref) => ref.watch(fooProvider(100))),
],
child: Consumer(
builder: (context, ref, child) => Text("${ref.watch(barProvider)}"),
),
),
ref.refreshとref.invalidate
プロバイダーを初期状態に戻し再評価する
final fooProvider = StateProvider((ref) => 0);
class FooWidget extends StatelessWidget {
Widget build(BuildContext context) => Consumer(
builder: (context, ref, child) => TextButton(
child: Text("reset"),
onPressed: () => ref.refresh(fooProvider)
),
);
}
以下は、invalidate
のrefresh
との違いに関するドキュメントの引用
refreshとは対照的に、リフレッシュは即時ではなく、代わりに次の読み込みまたは次のフレームに遅れて行われます
Discussion