- Google標準
- Googleもメインでこの方法を利用している
void main() {
runApp(
const ProviderScope(
child: MyApp(),
),
);
}
Method |
Content |
ref.watch(SomeProvider) |
値の変化を監視. 値が変化したら対象のWidgetをリビルド. |
ref.read(SomeProvider) |
値を監視せず, その時のProviderにアクセスして値を参照. リビルドはしない. |
ref.listen(SomeProvider, (prev, next) { ... }) |
値の変化を監視をする. リビルドはしない. |
ref.refresh(SomeProvider) |
プロバイダーを即時初期状態に戻して, 再評価する. |
ref.invalidate(SomeProvider) |
次のフレームでプロバイダーの状態を戻して, 再評価する. |
// ref.watch
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
ref.watch(SomeProvider);
},
);
// ref.read
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
ref.read(SomeProvider);
},
);
// ref.listen
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
ref.listen(SomeProvider, (pref, next) {
...
},
),
);
// ref.refresh
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return ElevatedButton(
onPress: () => ref.refresh(SomeProvider),
child: const Text('Reset provider'),
);
},
);
// ref.invalidate
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
onPress: () => ref.refresh(SomeProvider),
child: const Text('Invalidate provider'),
},
);
Providerアクセス時のリビルド条件を制御する
- SomeProvider.select((Repository) => monitoredValue)
// 状態変数
class Repository {
final int? bar;
final int? baz;
const Repository({this.bar, this.baz});
}
// Provider
final repoProvider = SomeProvider<Repository>((Ref ref) => Repository(10, 15));
~~~
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
// 状態変数(Repository.bar)の変化のみを監視する
var bar = ref.watch(repoProvider.select((repo) => repo.bar));
return Text($bar);
},
);
Providerから他のProviderへのアクセス
- worldProviderの値が変化するとMonitorWidgetがリビルドする.
helloProvider ---> worldProvider
↑ 間接 ↑
----------------- MonitorWidget
e.g.
final worldProvider = SomeProvider((Ref ref) => 0);
final helloProvider = SomeProvider((Ref ref) => ref.watch(worldProvider) * 2);
class MonitorWidget extends StatelessWidget {
const MonitorWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return Text(ref.watch(helloProvider);
},
);
}
}
- アクセス権限などの制御関連やStream専用のProviderなど様々な種類が存在する.
Provider |
Note |
Provider |
利用者に値の操作はさせず, 参照のみ許可 |
StateProvider |
利用者に値の操作と参照の両方を許可 |
StateNotifierProvider |
利用者に値の操作と参照の両方を許可. ただし, 操作方法は, 公開しているメソッドを使用する. |
ChangeNotifierProvider |
ミュータブルな状態を扱うStateNotifierProvider. |
FutureProvider |
非同期なFuture処理を扱うProvider. 値の参照のみ許可 |
StreamProvider |
非同期なStream処理を扱うProvider. 値の参照のみ許可 |
e.g.
final fooProvider = StateProvider<int>((Ref ref) => 0);
final barProvider = StateProvider.family<int, int>((Ref ref, int v) {
return ref.watch(fooProvider) * v;
});
~~~
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return Text(ref.watch(barProvider(100)));
}
);
final fooProvider = StateProvider.family<int,int>((Ref ref, int value) => value);
final barProvider = StateProvider<int>((Ref ref) => throw UnimplementedError());
ProviderScope(
overrides: [
barProvider.overrideWith((Ref ref) => ref.watch(fooProvider(100)),
],
child: Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return Text(ref.watch(barProvider);
},
),
);
final fooProvider = StateProvider<int>((Ref ref) => 0);
ProviderScope(
overrides: [
fooProvider.overrideWith((Ref ref) => 100),
],
child: Consumer(
builder: (BuildContext context WidgetRef ref, Widget child) {
return Text(ref.watch(fooProvider);
},
),
);
- 利用されなくなったプロバイダーをGCで自動削除する
- また, onDispose()で削除するときにコールバックを設定できる.
- HTTPリクエストが完了する前にプロバイダーが削除されそうな際, リクエストをキャンセルする用途など.
final fooProvider = FutureProvider.autoDispose<Foo>((Ref ref) async {
// HTTPリクエストをキャンセルするのに使用するトークン
final token = CancelToken();
// プロバイダーが削除されたら呼ばれるコールバックでHTTPリクエストをキャンセルする.
ref.autoDispose(() => token.cancel());
var res = await Dio().get('https://domain/your/path/to', cancelToken: token);
return Foo.fromResponse(res);
});
final fooProvider = Provider((Ref ref) => "foo");
class FooWidget extends StatelessWidget {
const FooWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context, Ref ref, Widget child) {
return Text(ref.watch(fooProvider));
},
);
}
}
final fooProvider = StateProvider((Ref ref) => 0);
class FooWidget extends StatelessWidget {
const FooWidget({super.key});
@override
Widget build(BuildContext context) {
~~~
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return Text(ref.watch(fooProvider).state);
},
);
~~~
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return ElevatedButton(
onPressed: () => ref.read(fooProvider.notifier).state++;
);
}
);
}
}
- 利用者に値の操作と参照を許可する. ただし, 操作方法は, 公開しているメソッド経由のみ.
- StateNotifierProviderで管理する状態はイミュータブルが前提.
@immutable // 不変
class ImmutableFoo {
final int value;
const Foo(this.value);
}
class FooController extends StateNotifier<ImmutableFoo> {
FooController() : super(const ImmutableFoo(0));
void increment() {
state = ImmutableFoo(state.value + 1);
}
}
~~~
final fooProvider = StateNotifierProvider<FooController, ImmutableFoo>((Ref ref) => FooController());
class FooWidget extends StatelessWidget {
const FooWidget({super.key});
@override
Widget build(BuildContext context) {
~~~
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) => Text(ref.watch(fooProvider).value),
);
~~~
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
ref.read(fooProvider.notifier/*State*/).increment();
}.
);
}
}
- ミュータブルな状態を扱うStateNotifierProvider
- イミュータブルな状態を扱う場合はStateNotifierProviderを使う
class MutableFoo {
int value; // ※ final修飾子がついていない!
MutableFoo(this.value);
}
class FooController extends ChangeNotifier {
MutableFoo foo;
FooController() : foo = MutableFoo(0);
void increment() {
foo.value++;
notifyListeners(); // notifyListeners()を呼び出して変更を通知する.
}
}
~~~
final fooProvider = ChangeNotifierProvider((Ref ref) => FooController());
class FooWidget extends StatelessWidget {
const FooWidget({super.key});
@override
Widget build(BuildContext context) {
~~~
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) => Text(ref.watch(fooProvider).value),
);
~~~
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
ref.read(fooProvider.notifier/*State*/).increment();
}.
);
}
}
- 非同期処理を行う. 値の参照のみ.
- 非同期処理の進行状態を
loading
, error
, data
の3種類で管理する
final fooProvider = FutureProvider<String>((Ref ref) async {
await Future.delayed(const Duration(seconds: 3));
return "foo after 3 [s]";
});
~~~
class FooWidget extends StatelessWidget {
const FooWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return ref.watch(fooProvider)
.switch(
data: (foo) => Text(foo),
error: (e, st) => const Text('Future Error'),
loading: => const Text('Loading...'),
);
},
);
}
}
- Stream(非同期なイベント)を扱うプロバイダ
- FutureProviderのStream版
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stream_map/model/user.dart';
final fooProvider = StreamProvider((Ref ref) {
// Firebase with firestore
final collection = FirebaseFirestore.instance.collection('users');
// データ (Map型)を取得
final stream = collection.snapshots().map(
// CollectionのデータからUserクラスを生成
(elem) => elem.docs.map((e) => User.fromJson(e.data())).toList();
//final controller = StreamController();
//controller.add("Hello");
return controller.stream; // Stream<User?>
});
~~~
class FooWidget extends StatelessWidget {
const FooWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
final asyncValue /* AsyncValue<User?> */ = ref.watch(streamProvider);
return asyncValue.switch(
data: (user) => user != null ? MyUserPage() : SignupPage(),
loading: () => const CircularProgressIndicator(),
error: (err, st) => Text("Error: ${err}, StackTrace: ${st}"),
);
},
);
}
}
Widget |
Content |
StatelessWidget |
- |
Consumer |
Providerアクセスの最小単位 |
ConsumerWidget |
StatelessWidget + Consumer. Widget全体がリビルド対象になるので注意. |
StatefulWidget |
- |
State
|
- |
ConsumerState
|
- |
StatelessWidget & Consumer
class SomeStatelessWidget extends StatelessWidget {
const SomeStatelessWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return Text(ref.watch(SomeProvider));
},
); // Consumer
}
}
- Widget全体がリビルド対象になるので注意.
- 一部だけリビルドで再描画する場合は, 個別にConsumerを使う方針がベスト.
class SomeConsumerWidget extends ConsumerWidget {
const SomeConsumerWidget({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
~~~
ref.watch(SomeProvider); // 値の変化を監視. 値が変化したらSomeConsumerWidget全体をリビルド
~~~
ref.read(SomeProvider); // 値を監視せず, その時点のProviderが提供する状態にアクセスする. リビルドしない.
}
}
class SomeStatefulWidget extends StatefulWidget {
const SomeStatefulWidget({super.key});
@override
State<SomeStatefulWidget> createState() => SomeState;
}
class SomeState extends State<SomeStatefulWidget> {
@override
void initState() {
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
void didUpdateWidget(SomeStatefulWidget oldWidget) {
super.didUpdateWidget(oldWidget)
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer(
builder: (BuildContext context, WidgetRef ref, Widget child) {
return Text(ref.watch(SomeProvider);
},
);
}
}
- 元々のStateクラスによる状態管理
- Providerによる状態管理
- 上記の2つの状態管理手法が混ざる
class SomeStatefulWidget extends StatefulWidget {
const SomeStatefulWidget({super.key});
@override
ConsumerState<SomeStatefulWidget> createState() => SomeConsumerState;
}
class SomeConsumerState extends State<SomeStatefulWidget> {
const SomeConsumerState({super.key});
@override
void initState() {
super.initState();
ref.read(SomeProvider);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
void didUpdateWidget(SomeStatefulWidget oldWidget) {
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context, WidgetRef ref, Widget child) {
return Text(ref.watch(SomeProvider);
}
}