From 3069b5696bd5fd82110870aa8ba21ec773e8a3c1 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 6 Oct 2023 23:04:22 +0600 Subject: [PATCH] docs: queryclient and query cache --- docs/docs/basics/MutationJob.md | 26 -------- docs/docs/basics/QueryClientProvider.md | 82 ++++++++++++++++++++++- docs/docs/basics/QueryJob.md | 87 ------------------------- 3 files changed, 81 insertions(+), 114 deletions(-) delete mode 100644 docs/docs/basics/MutationJob.md delete mode 100644 docs/docs/basics/QueryJob.md diff --git a/docs/docs/basics/MutationJob.md b/docs/docs/basics/MutationJob.md deleted file mode 100644 index 52235ea..0000000 --- a/docs/docs/basics/MutationJob.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -title: Mutation Job -sidebar_position: 5 ---- - -`MutationJob` is just like [`QueryJob`](/docs/basics/QueryJob) but for `Mutations`. -It is used to define how & where the new data is inserted or existing data is updated or deleted. Basically, it's the kind of Job that you'll use with http `POST/PUT/DELETE` requests but it doesn't have to be just HTTP requests, it can be anything that returns as long as it returns Future - -Here's a simple example: - -```dart -final basicMutationJob = MutationJob>( - mutationKey: "basic-mutation-example", - task: (key, data) async { - final response = await http.post( - Uri.parse("https://jsonplaceholder.typicode.com/posts"), - headers: {'Content-type': 'application/json; charset=UTF-8'}, - body: jsonEncode(data), - ); - return jsonDecode(response.body); - }, -); -``` - -Here, instead of a `queryKey` there's a `mutationKey` parameter that is used to identify the Job. -`Mutation` also supports _retries_ but instead of `externalData` `Mutations` has `variables` parameter that used when the `mutate` method of Mutation is called where you can pass outside data to the `Mutation.task` \ No newline at end of file diff --git a/docs/docs/basics/QueryClientProvider.md b/docs/docs/basics/QueryClientProvider.md index 6e8d728..9fedc0f 100644 --- a/docs/docs/basics/QueryClientProvider.md +++ b/docs/docs/basics/QueryClientProvider.md @@ -2,6 +2,8 @@ sidebar_position: 1 --- +### QueryClientProvider widget + The first thing needed for storing any form of data is a store. QueryClientProvider is basically a `InheritedWidget` which wraps around the actual store `QueryClient`. You must use wrap your `MaterialApp` or `CupertinoApp` with `QueryClientProvider` for using same `QueryClient` across all screens/routes. Or, if you want you can use `QueryClientProvider` anywhere in the Widget Tree to a different `QueryClient` to the descendant widgets ```dart @@ -43,4 +45,82 @@ class MyApp extends StatelessWidget { > If you provide `QueryClient` to `QueryClientProvider` then assign all the parameters to `QueryClient` itself -For more information on how to use QueryClientProvider, please refer to the [QueryClientProvider](https://pub.dev/documentation/fl_query/latest/fl_query/QueryClientProvider-class.html) API Reference \ No newline at end of file +For more information on how to use QueryClientProvider, please refer to the [QueryClientProvider](https://pub.dev/documentation/fl_query/latest/fl_query/QueryClientProvider-class.html) API Reference + + +## QueryClient widget + +The `QueryClient` is the store that holds all the query/mutation data. You can create a new instances of `QueryClient` by calling `QueryClient()` constructor. But it is recommended to use `QueryClientProvider` to create a new instance of `QueryClient` and use it across the app. +`QueryClient` can be accessed from anywhere in the widget tree using `QueryClient.of(context)`. It can be useful for imperative data manipulation + +Often there are cases where imperative access to the API is necessary. e.g. invalidating/refreshing a query from another page after a mutation +`QueryClient` gives access to the base of this framework. SO BE CAREFUL WHILE USING IT + +```dart +final queryClient = QueryClient.of(context); +``` + +You can create/pre-fetch a query like this: + +```dart +await queryClient.fetchQuery( + 'todos', + () => api.getTodos(), +); +``` +> `queryClient.fetchQuery` will create and fetch a query immediately if it's not already available. +> To just create a query and not fetch it, use `queryClient.createQuery` instead + +Also, you can refresh queries or queries that start with a certain prefix: + +```dart +// Refresh a single query +await queryClient.refreshQuery( + 'todos', + exact: true // pass false if you want to refresh a query with prefix +); + +// Refresh multiple queries passing multiple keys +await queryClient.refreshQueries(['todos', 'posts']); + +// Refresh queries with prefix +await queryClient.refreshQueriesWithPrefix('todo/'); +``` + +:::tip +You can also use `QueryClient` to create, get, refresh/mutate `InfiniteQuery`(s) & `Mutation`(s) +::: + +## QueryCache + +Uhm, actually we kinda lied. QueryClient technically holds all the data but truly `QueryCache` is the one +that truly holds all the query and mutations. `QueryClient` is just a wrapper around `QueryCache` that provides some +useful methods and properties. `QueryCache` can be accessed using `QueryClient`'s cache property but it doesn't have any +useful methods or properties. So, it is recommended to use `QueryClient` instead of directly accessing the `QueryCache` + +Also while using `QueryClient` you don't need to worry about `QueryCache` at all. It is just for the sake of knowledge + +### Deleting queries/mutations from cache + +One thing noticeable is there's no way to delete query/mutation using `QueryClient`. You have to use `QueryCache` for +that part. It is the only reason ever to use QueryCache + +:::warning +Accessing the `QueryCache` directly is not recommended. And altering the cache can lead to unexpected behavior and +potential crashes +::: + +Here's how to delete a query/mutation from cache: + +```dart +final queryClient = QueryClient.of(context); +final query = queryClient.getQuery('todos'); +final mutation = queryClient.getMutation('add-todos'); + +queryClient.cache.removeQuery(query); +queryClient.cache.removeMutation(mutation); +``` +:::warning +Deleting a query/mutation from cache can be dangerous as it can cause memory leaks, infinite re-renders and crash if the +`Query`, `InfiniteQuery` or `Mutation` is still in use and mounted +::: diff --git a/docs/docs/basics/QueryJob.md b/docs/docs/basics/QueryJob.md deleted file mode 100644 index 02da82e..0000000 --- a/docs/docs/basics/QueryJob.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: Query Job -sidebar_position: 2 ---- - -Query Jobs are what you use to define the logic how or from where the data is fetched/queried. It is where the `task` function is defined. `QueryJob` is reusable throughout application - -Here's a simple example - -```dart -final job = QueryJob( - queryKey: "a-unique-key", - task: (queryKey, externalData){ - return Future.delayed(Duration(seconds: 1), () => "Hello World"); - } -); -``` - -The `queryKey` must be unique. It is used to identify the job - -The `task` callback has to be asynchronous. When the `task` is run by `Query` the `queryKey` & the `externalData` passed from `QueryBuilder` is passed to it as parameters. The externalData can be anything. You can provide a Generic Type parameter for it too - -:::info -If `externalData` is of an `Iterable` type (`Map`, `List`, `Set` etc), it will be compared [shallowly](https://medium.com/nerdjacking/shallow-deep-comparison-9fd74ac0f3d2) -::: - -### External Data - -A more real-world example of `QueryJob` with `externalData` - -```dart -import 'package:fl_query/fl_query.dart'; -import 'package:http/http.dart'; - -final anotherJob = QueryJob( - queryKey: "another-unique-key", - task: (queryKey, httpClient){ - return httpClient.get("https://jsonplaceholder.typicode.com/todos/1").then((response) => response.body);; - } -); -``` - -Here `externalData` is a configured `Client` from the `http` package. - -By default when `externalData` changes or updates the query is not refetched but if you want it to refetch when the `externalData` changes, you can set `refetchOnExternalDataChange` property of `QueryJob` to `true`. If you want this behavior globally to be enabled then you can set `refetchOnExternalDataChange` property of [QueryBowlScope](/docs/basics/QueryBowlScope) to `true` - - -```dart -import 'package:fl_query/fl_query.dart'; -import 'package:http/http.dart'; - -final anotherJob = QueryJob( - queryKey: "another-unique-key", - refetchOnExternalDataChange: true, - task: (queryKey, httpClient){ - return httpClient.get("https://jsonplaceholder.typicode.com/todos/1").then((response) => response.body);; - } -); -``` - -Now every time when the externalData changes the query will refetched. - -### Retries - -When a query returns an `Exception` or in other word, fails, the query is re-run multiple times in the background until it succeeds or the retry limit is reached. You can configure the retry behavior of query by modifying `retries` & `retryDelay` properties of `QueryJob` - -- `retries`: is amount of times the query will be retried before setting the status as `QueryStatus.error`. If its zero, it will not retry. - -- `retryDelay`: is the `Duration` between retries. That means after what amount of duration the retries will take place until it succeeds or the retry limit is reached. - -By default `retries` is `3` and `retryDelay` is `Duration(milliseconds: 200)` - - -```dart -final job = QueryJob( - queryKey: "exceptional-query", - retries: 10, - retryDelay: Duration(milliseconds: 200), - task: (queryKey, _) async { - throw Exception("I'm an evil Exception"); - } -); -``` - -Now the query will be retried 10 times with a delay of 200ms between each retry - -There are more properties of `QueryJob` that you can configure. See the API reference of [QueryJob](https://pub.dev/documentation/fl_query/latest/fl_query/QueryJob-class.html) \ No newline at end of file