クエリを整理する
クエリごとに定義を 1 つにすると、キーとデータ型がすべての画面、Bloc、サービスで同じになります。定義は、呼び出す API の隣のファイルに置いてください。
const todosKey = ['todos', 'list'];
final todosQuery = Query( queryKey: todosKey, queryFn: (_) => api.getTodos(),);
Query<Todo> todoQuery(int id) => Query( queryKey: ['todos', 'detail', id], queryFn: (_) => api.getTodo(id), );クエリはデータを持たないので、トップレベルの final で定義できます。ID などの値を受け取るクエリは関数にします。
データを使うすべての場面で、同じ定義を使います。
QueryBuilder(query: todoQuery(id), builder: ...); // in a widgetfinal todo = await client.query(todoQuery(id)); // fetching outside widgetsclient.updateData(todoQuery(id), (todo) => todo?.copyWith(done: true));final todos = todosQuery.observe(); // in a cubit or a servicetodosQuery のすべてのウィジェットとオブザーバーは、同じキャッシュエントリを共有します。ミューテーションは、キーを書き直さずに todosKey を無効化します。InfiniteQuery の定義も同じように使えます。
- 型が常にチェックされます。ウィジェット、
client.query、getData、setData、updateDataはクエリからデータ型を受け取ります。そのため、キャストは不要で、キーに別の型を書き込むこともできません。 - キーが一貫します。キーを打ち間違えると、気づかないうちに 2 つ目のキャッシュエントリができます。クエリごとに定義を 1 つにすれば、このミスは起きません。
- 階層が明確になります。
['todos', ...]が Todo に関するすべてをまとめるので、invalidateQueries(queryKey: ['todos'])でリストとすべての詳細を一度に更新できます。
クエリ関数に依存関係を渡す
Section titled “クエリ関数に依存関係を渡す”クエリ関数で BuildContext をキャプチャしないでください。上のコードの api は長く生存するオブジェクトなので、安全です。
Fuery はクエリ関数をキャッシュエントリと一緒に保持し、後で再び実行します。
- アプリがフォアグラウンドに戻ったとき
- ネットワークに再接続したとき
refetchIntervalの周期ごと- 何かがキーを無効化または再取得したとき
このうち一部は、クエリを作ったウィジェットがなくなり、その BuildContext がアンマウントされた後に実行されます。キャプチャした State、TickerProvider、context 経由で読み取ったものにも、同じ問題があります。
単純な値は安全です。ID や検索語はキーとリクエストに含めるもので、ウィジェットがなくなっても残ります。
依存関係は、クエリのパラメーターとして渡してください。
Query<List<Todo>> todosQuery(TodoApi api) => Query( queryKey: todosKey, queryFn: (_) => api.getTodos(), );画面は、クエリを使う場所で依存関係を解決します。
QueryBuilder(query: todosQuery(locator<TodoApi>()), builder: ...)または、クエリ関数の中で依存関係を解決してください。こうするとクエリはパラメーターなしのままです。
final todosQuery = Query( queryKey: todosKey, queryFn: (_) => locator<TodoApi>().getTodos(),);locator は、アプリが依存関係の解決に使うものを表します。どちらの形でも、クロージャはウィジェットに結びついたものを含みません。同じキーの 2 つのウィジェットは同じキャッシュエントリを共有するので、キーを使うすべての場所で同じ依存関係を渡してください。
すべてのクエリ関数が受け取る引数 QueryFunctionContext は、ウィジェットツリーのものを何も持ちません。クエリ関数のコンテキストを参照してください。
リポジトリの失敗を報告する
Section titled “リポジトリの失敗を報告する”クエリ関数を失敗させるには、スローする必要があります。Fuery がエラー状態にするのは、スローされたエラーだけです。Result、Either、そのほかのラッパーを返す関数は、ラッパーの中身が何であっても常に成功します。その場合、クエリは次のようになります。
statusはQueryStatus.success、errorはnullのままです。isError、isLoadingError、isRefetchErrorは true になりません。- 再試行ポリシーはスローされたエラーしか見ないので、再試行しません。
クエリ関数の中で結果を取り出し、失敗をスローしてください。
Query<List<Todo>> todosQuery(TodoRepository repo) => Query( queryKey: todosKey, queryFn: (_) async => switch (await repo.getTodos()) { Ok(:final value) => value, Err(:final error) => throw error, }, );クエリのデータは null にできないので、返す結果がない関数もスローします。クエリのデータは null にできないを参照してください。
ミューテーションを整理する
Section titled “ミューテーションを整理する”ミューテーションは、クエリと同じ方法で定義し、クエリの隣に置いてください。それぞれに、変更するデータのキーから作った mutationKey を付けてください。
final addTodo = Mutation( mutationKey: [...todosKey, 'add'], mutationFn: (String title) => api.addTodo(title), onSuccess: (todo, title, context, client) => client.invalidateQueries(queryKey: todosKey),);mutationKey があると、どのウィジェット、フック、Cubit が開始した実行でも、どの画面からでもミューテーションの実行を見つけられます。MutationStateBuilder(mutation: addTodo) は、アプリのどこでも実行を表示します。ミューテーションのすべての実行を表示するを参照してください。
定義は状態を持たないので、クエリと同じくトップレベルの値にできます。各実行はクライアントのキャッシュに属します。コールバックはミューテーションを実行するクライアントを受け取るので、テストでも FueryProvider の下でも、キャッシュの操作は正しいクライアントに届きます。実行とキャッシュエントリの違いは、ミューテーションの実行で説明しています。
無効化やロールバックなどのキャッシュの操作は、定義に書いてください。呼び出しが成功した後に画面を閉じるなど、1 つの画面に関わる処理は、呼び出し側に書いてください。
onPressed: () async { try { await addTodo.mutateAsync(title, context.queryClient); } catch (_) { return; // A MutationStateListener reports the failure. } if (context.mounted) Navigator.pop(context);},ボタン全体のコードは、1 回の呼び出しが成功した後に処理するで紹介しています。
MutationStateListener は、ミューテーションのどの実行の後でも、どの画面からでも、その画面の BuildContext でスナックバーやダイアログを表示します。ミューテーションの失敗をユーザーに伝えるを参照してください。
サンプルアプリでは
Section titled “サンプルアプリでは”サンプルアプリは、クエリをフィードのクエリに、ミューテーションをフィードのミューテーションに定義しています。README では、各画面とその画面で示している機能を対応づけています。