10月の中旬ごろにリニューアル中だったRiverpodの公式ドキュメントが完成したようです。
最新版を読み直して気づいたことなどをまとめていければと思います。
riverpod_listというRiverpod用のlintルールを適用できるパッケージがあるようです。
ができるようなので、入れておくのが良いと思います。
riverpodの作者も導入を推奨しているようです。
VSCodeの拡張機能。
providerの記述が楽になるかもです。
riverpod_generatorを使っている場合はProviderを自ら記述する機会があまりないと思うので、導入するメリットが少なくなるかもです。
https://marketplace.visualstudio.com/items?itemName=robert-brunhage.flutter-riverpod-snippets
メリット
APIからのレスポンスをそのままstateに入れることができるのであればシンプルに書ける。
デメリット
例
Future<void> addTodo(Todo todo) async {
// The POST request will return a List<Todo> matching the new application state
final response = await http.post(
Uri.https('your_api.com', '/todos'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(todo.toJson()),
);
// We decode the API response and convert it to a List<Todo>
List<Todo> newTodos = (jsonDecode(response.body) as List)
.cast<Map<String, Object?>>()
.map(Todo.fromJson)
.toList();
// We update the local cache to match the new state.
// This will notify all listeners.
state = AsyncData(newTodos);
}
メリット
デメリット
例
Future<void> addTodo(Todo todo) async {
// We don't care about the API response
await http.post(
Uri.https('your_api.com', '/todos'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(todo.toJson()),
);
// Once the post request is done, we can mark the local cache as dirty.
// This will cause "build" on our notifier to asynchronously be called again,
// and will notify listeners when doing so.
ref.invalidateSelf();
// (Optional) We can then wait for the new state to be computed.
// This ensures "addTodo" does not complete until the new state is available.
await future;
}
メリット
デメリット
例
Future<void> addTodo(Todo todo) async {
// We don't care about the API response
await http.post(
Uri.https('your_api.com', '/todos'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(todo.toJson()),
);
// We can then manually update the local cache. For this, we'll need to
// obtain the previous state.
// Caution: The previous state may still be loading or in error state.
// A graceful way of handling this would be to read `this.future` instead
// of `this.state`, which would enable awaiting the loading state, and
// throw an error if the state is in error state.
final previousState = await future;
// We can then update the state, by creating a new state object.
// This will notify all listeners.
state = AsyncData([...previousState, todo]);
}
autoDisposeをなるべくつけておいた方が良い。
引数毎にインスタンスが生成されるため、メモリリークの原因になりやすい。
同じ引数かどうかの判定にあたり、引数の ==
オペレータを頼りに判別するため、引数に入れた変数が ==
オペレータをオーバーライドしていない場合は、同じ引数判定ができない。
具体例
ref.watch(activityProvider(['recreational', 'cooking']));
['recreational', 'cooking'] == ['recreational', 'cooking']
はfalse
になるため、同じ引数判定ができない。
=> riverpod_listで警告が出るはず。
具体例
void onTap() {
// Invalidate all possible parameter combinations of this provider.
ref.invalidate(labelProvider);
// Invalidate a specific combination only
ref.invalidate(labelProvider('John'));
}
ProviderObserverを使えば以下の4つイベントが取得できる。
- `didAddProvider`, called when a provider is added to the tree
- `didUpdateProvider`, called when a provider is updated
- `didDisposeProvider`, called when a provider is disposed
- `providerDidFail`, when a synchronous provider throws an error
非同期の(asynchronous)providerの場合はAsyncErrorでなく、didAppProvider,didUpdateProvider側でエラーがキャッチするようになっている
⇒ riverpod3.0以降で修正予定のようです。
ref.refresh = invalidate + read
。invalidateした後に最新値を取得したい場合はrefresh
を使うと良い。
T refresh<T>(provider) {
invalidate(provider);
return read(provider);
}
Widget内のinitStateでなく、遷移処理の直前の方が良い。
Don't
class WidgetState extends State<MyWidget> {
void initState() {
super.initState();
// Bad: the provider should initialize itself
ref.read(provider).init();
}
}
Do
遷移処理前に初期化しておく。
ElevatedButton(
onPressed: () {
ref.read(provider).init();
Navigator.of(context).push(...);
},
child: Text('Navigate'),
)
Don't
class Example {
// Unsupported operation. Could cause memory leaks and unexpected behaviors.
final provider = Provider<String>((ref) => 'Hello world');
}
Do
final provider = Provider<String>((ref) => 'Hello world');
Exampleクラスのインスタンスがプロジェクト内で1つではない場合に、providerがdisposeされずに永遠に残ってしまうとかかな?
シングルトンなどを使っていない場合は、同じインスタンスにアクセスしようと思ってもできなかったり?
ちょっと意図が微妙に掴めていないかも。
Widget内部のローカルの状態管理はflutter_hooksを使いましょうとのこと。
providerはtop-levelのfinal変数として扱われるべきだという話もあり、他の画面などからローカルのStateにアクセスできてしまうのは良くないという理由でしょう。
providerをプライベート変数にするなどで解決もできそうですが、公式ドキュメント通りに実装するということであれば、flutter_hooksを使う実装の方が良いのかもしれませんね。
stateのその他プロパティがほとんど更新されないケース。
selectを使用すると、個々の読み取り操作が若干遅くなり、コードの複雑さがほんの少し増すため、他のプロパティがほとんど変更されないのであれば、selectを使って購読するプロパティを限定する価値がないようです。
今回はriverpodのリニューアルしたドキュメントで学んだことを列挙してみました。
個人的に新たに学べた場所を列挙しただけであり、人によって学べる内容は違うので、一度じっくり読んでみることを推奨します。
読んでいただいた方に新しい発見があれば幸いです。
可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More可茂IT塾ではFlutterインターンを募集しています!可茂IT塾のエンジニアの判断で、一定以上のスキルをを習得した方には有給でのインターンも受け入れています。
Read More