Riverpodのドキュメントを意訳してみた1
今回、意訳したページは以下のリンク先です。意訳は正確でないこともあるのであらかじめご了承ください。Providerは異なった方法で生成されるが動作は一緒 一般的に以下のように、グローバル定数として宣言される
こんにちは。 私は、現在FlutterアプリにRiverpod導入を考えています。導入に先立ってRiverpodの公式ドキュメントを読みました。 いつかドキュメント全体をざっくり見返すときにあまり頭を使いたくないため、メモの意味も兼ねてドキュメントを意訳して記事にまとめておくことにしました。
今回、意訳したページは以下のリンク先です。
意訳は正確でないこともあるのであらかじめご了承ください。
Providerをなぜ使うのか
- 様々な場所から状態へのアクセスを簡単にする
- 状態の組み合わせを単純にする
- パフォーマンスを最適化する
- テスタビリティを向上させる
- 簡単に統合する
Providerの生成
Providerは異なった方法で生成されるが動作は一緒 一般的に以下のように、グローバル定数として宣言される
final myProvider = Provider((ref) {
return MyValue();
});
Note
Providerをグローバル変数で宣言することを恐れる必要はない
Providerは完全に変更不可能だ。Providerを宣言することは関数を宣言することと変わらない、そしてテストやメンテナンスをしやすい
このスニペットは3要素から成り立っている - final myProviderという変数宣言 将来的にProviderから状態を読み込むための変数で、つねに変更不可能だ。
-
Provider、使用するProviderのp種類Providerは一番基本的なProviderだ。二度と変更されないオブジェクトを公開する。状態をインタラクティブに変更するためにProviderをStreamProviderやStateNotifierProviderに変更することもできる。 -
状態を共有する関数 関数は常に
refとよばれるオブジェクトを引数で受け取る。このオブジェクトはほかのProviderを読み取ることや、Providerが破棄されるときになんらかの処理を実行することを可能にする(フック的なやつ)。
関数によって作られたオブジェクトの型は、使用されるProviderに依存したProviderに渡される。 例えば、Providerの関数はなんらかのオブジェクトを生成できる。一方、StreamProviderのコールバックはStreamを返すだろう。
Info
宣言可能なProviderはどれも制限を必要としない。package:providerを使用した場合とは逆に、Riverpodでは同じ型のProviderを公開できる。 実際、ふたつのProviderはどちらもStringを生成しているが、問題はない。
Caution
Providerを使用する時、ProviderScopeをFlutterApplicationsのルートに追加してください。
Stateが破棄される前の処理の実行について
時には、状態についてのProviderは破棄・再生成される。一般的な使用例は、providerが破棄される前に状態を破棄する。たとえばStreamControllerをcloseするように。
これはref.onDispose()を使用することで実現する。
final example = StreamProvider.autoDispose((ref) {
final streamController = StreamController<int>();
ref.onDispose(() {
streamController.close();
});
return streamController.stream;
}
Note
使用するProviderによっては、事前にProviderを破棄する方法が用意されているかもしれない。 たとえば、StateNotifierProviderはStateNotifierのdisposeメソッドを呼ぶ
ProviderのModifierについて
すべてのProviderには、さらなる機能をついかする方法が用意されている。 refに新しい特徴を追加するか、ProviderをConsumeする方法を少し変更している。
final myAutoDisposeProvider = StateProvider.autoDispose<String>((ref) => 0);
final myFamilyProvider = Provider.family<String, int>((ref, id) => '$id');
ときには、二種類のModifiersを使用することが可能だ
.autoDispose, 参照されなくなった時にProviderの状態を自動で破棄する.family, 外部のパラメータからProviderの生成を可能にする。
Note
はじめに複数のmodifiersを使う時
final userProvider = FeatureProvider.autoDispose.family<User, int>(ref, userId) async {
return fetchUser(userId);
});
Providerをreadする
このガイドを読む前にread about Providersを読んね
このガイドでは、どうやってProviderを使用するのかを知ることができる
providerをreadする方法はいくつかあって、基準・規格・仕様によって少し違う 手短に知るには以下の図にそってどのproviderをreadするかみてね
どこでProviderをreadするか? - テストやDart-Only-PackageでreadするならProviderContainer.read
-
Widgetの中
- Dialog表示のためにlisteningするならProviderListener
onPressedを覗くbuildメソッドの中でreadしなければContext.read- flutter-hooksを使うなら
useProvider - flutter-hooksを使わないなら
Consumer
-
他のProviderの中
- providerの更新に伴ってproviderによって公開された値が、再生成されるべきか
- べきなら
ProviderReference.watch - ちがうなら
providerReference.read
- べきなら
- providerの更新に伴ってproviderによって公開された値が、再生成されるべきか
つぎにそれぞれ個別の場合をみながらどうやって動作するのか紹介します
このガイドでは以下のproviderで考えます。
final counterProvider = StateProvider((ref) => 0);
readの方法を決定する
listenしたいProviderによって、値をlistenする方法がいくつかあります
たとえば、以下のStreamProviderについてだと、
final userProvider = Streamprovider<User>(...);
userProviderをreadするとき、以下のような方法が可能である。
- userProvider自身で同期的に現在の状態をreadする
Widget build(BuildContext context, ScopeReader watch) {
AsyncValue<User> user = watch(userProvider);
return user.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => const Text('Oops'),
data: (user) => Text(user.name),
);
}
- Streamの連携に伴って変更される値をreadするなら
userProvider.stream
Widget build(BuildContext context, ScopeReader watch) {
Stream<User> user = watch(userProvider.stream);
}
- 最新の値で発火されるFutureで解決される値を取得するには、
userProvider.lastを使用する
Widget build(BuildContext context, Scopedreader watch) {
Future<User> user = watch(userProvider.last);
}
さらなる情報はAPIリファレンスみてね
ProviderをWidgetの中で使う
この章では、ProviderをWidgetの中で使う方法について知ることができる
ConsumerWidget
ConsumerWidgetはStatelessWidgetのような基底クラスだ。そして、providerをlistenすることができる
class Home extends ConsumerWidget {
@override
Widget build(BuildContext context, Scopedreader watch) {
int count = watch(counterProvider).state;
return Scaffold(
appBar: AppBar(title: const Text('Counter example'));
body: Center(
child: Text('$count'),
),
);
}
}
この例では、counterProviderとcounterが変更される旅にTextがリビルドされるだろう。
counterProviderの値によって気づくことは、関数がwatchを呼ぶことだ。ConsumerWidgetで作られた関数は私たちのproviderをlistenし、値の変更が公開されるとリビルドする。
Caution
ConsumerWidgetの引数を渡すwatch()は```onPressedのような関数では非同期で呼ばないべきだ。
もしユーザのイベントのレスポンスをproviderをreadする必要があるなr、myProvider.read(BuildContext)を使用してください
Consumer
Consumerはアプリケーション内でデータを扱う特定のウィジェットの再描画のパフォーマンスを最適化することができるConsumerWidgetだ
たとえば、ConsumerWidgetのスニペットを更新以下のコードでは、まえもってTextのみが再描画されることを認識できる。
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter example')),
body: Center(
child: Consumer(
builder: (context, watch, child) {
int count = watch(counterProvider).state;
return Text('$count');
},
),
),
);
}
}
context.read(myProvider)
ときには、listenする値が存在しないこともある。例えば、オブジェクトはonPressedでのみ必要な場合。
私たちはConsumerを使うことができる
Consumer(builder: (context, watch, _) {
StateController<int> counter = watch(counterProvider);
return RaisedButton(
onPressed: () => counter.state++,
child: Text('increment');
)
});
しかし、これは効果的ではない。providerのlistenによって、counterが変更される旅にRaisedButtonが再描画される。counterが実際にbuildに使われない場合でさえ。
解決方法としてcontext.read(myProvider)が存在する。
これを使うことで前述のコードを以下のようにリファクターできる。
@override
Widget build(BuildContext context) {
retur RaisedButton(
onPressed: () => context.read(counterProvider).state++,
child: Text('increment'),
);
}
こうすることで、依然として、クリックすることで、ボタンはcounterをインクリメントする。しかし、もはやproviderをlistenしておらず、不必要な再描画を避けている。
context.readがみつからない。どうしたらいい?
もしcontext.readが見つからないなら、それはおそらく正しいパッケージをインポートしていないからだ。このメソッドを使うにはpackage:flutter_riverpodか、package:hooks_riverpodをインポートしなければならない。
Info
listenしているProviderによっては、これは必要ないかもしれない。 たとえば、StateNotifierProviderはそれ自身の状態のlistenを除く、StateNotifierを取得する方法が用意されている。
class Counter extends StateNotifier<int> {
Counter(): super(0);
void increment() => state++;
}
final counterProvider = StateNotifierProvider((ref) => Counter());
// ...
@override
Widget build(BuildContext context, ScopedReader watch) {
final Counter counter = watch(counterProvider);
return RaisedButton(
onPressed: counter.increment,
child: Text('increment'),
);
}
Caution
context.readをbuild()の中で呼ぶことを回避してください。もし、再描画を最適化したいなら、値の変更のlistenをProviderの中に変更してください。
ProviderListener
ときには、Providerの変更のあとでダイアログの表示やウィジェットツリーへのルートの挿入を行いたい場合もある。
ProviderListenerウィジェットを利用して以下のように実装してください
Widget build(BuildContext context) {
return ProviderListener<StateController<int>>(
provider: counterProvider,
onChange: (counter) {
if(counter.state == 5) {
show Dialog(...);
}
},
child: Whatever(),
)
}
これできっとカウントが5になるとダイアログが表示されます。
Providerの中で他のProviderをreadする
一般的なProviderの作成方法として他のオブジェクトからオブジェクトを生成することがある。 例えば、UserRepositoryからUserControllerを生成したい時、それらは違ったProviderによって公開されているとする。
このシナリオはproviderがパラメータを受け取るためのrefを使うことによって可能になる
final userRepositoryProvider = Provider((ref) => UserRepository());
final userControllerProvider = StateNotifierProvider((ref) {
return UserController(
repository: ref.watch(userRepositoryProvider),
);
});
CombineProvidersガイドでより多くの情報を得られる。例えば以下のように - 時間と共に変化する値からオブジェクトを作る時、どんなことがおこるか - 良い方法について
-––