diff --git a/docs/docs/basics/DynamicMutations.md b/docs/docs/basics/DynamicMutations.md deleted file mode 100644 index 705ef51..0000000 --- a/docs/docs/basics/DynamicMutations.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Dynamic Mutations -sidebar_position: 9 ---- - -Just like [Dynamic Queries](/docs/basics/DynamicQueries), `MutationJob.withVariableKey` makes the mutation dynamic. Both of them are completely same - -```dart -final mutationVariableKeyJob = MutationJob.withVariableKey( - preMutationKey: "mutation-example", - task: (mutationKey, variables) { - return MyAPI.submit({...variables, id: getVariable(mutationKey)}); - }, -); -``` - -In the case of Mutation, we've `preMutationKey` instead of `preQueryKey` - -You can use the dynamic Mutation Job just like any other `MutationJob` except you've to invoke the defined dynamic mutation & pass the `variable-mutation-key` as the first argument. - - -```dart -MutationBuilder( - job: mutationVariableKeyJob(id), - builder: (context, mutation) {...}, -) -``` \ No newline at end of file diff --git a/docs/docs/basics/DynamicQueries.md b/docs/docs/basics/DynamicQueries.md deleted file mode 100644 index b5af108..0000000 --- a/docs/docs/basics/DynamicQueries.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Dynamic Queries -sidebar_position: 8 ---- - -All this time we've using queries by defining a Query Key in the `QueryJob`. But what if your widget needs to fetch data from of a dynamic id that is only known at runtime? This is where `QueryJob.withVariableKey` comes to play. It allows you to define query-key at runtime. It makes a query dynamic - -```dart -final queryVariableKeyJob = - QueryJob.withVariableKey( - prevQueryKey: "variable-query", - task: (queryKey, externalData) { - return Future.delayed( - const Duration(milliseconds: 500), - () => "QueryKey:${getVariable(queryKey)}", - ); - }, -); -``` - -Optionally, we can provide a `prevQueryKey` to make the QueryJob distinguishable from other dynamic Queries or just to keep them in a group. If you use `prevQueryKey`, you can use `getVariable` to extract the value of the variable from the `queryKey`. - -:::warning -Don't use `externalData` to make a query dynamic. Using `externalData` & `refetchOnExternalDataChange: true`, you may be able to achieve similar result but it'll replace the previously fetched data with the new data instead of creating a separate `Query` instance for the new variable-query-key & it's data -::: - -Now, let's use the Job with a QueryBuilder inside an actual widget: - -```dart -class ExampleState extends State{ - late double id; - @override - void initState() { - super.initState(); - id = Random().nextDouble() * 200; - } - - @override - Widget build(BuildContext context) { - return QueryBuilder( - job: queryVariableKeyJob(id.toString()), - externalData: null, - builder: (context, query) { - if (!query.hasData) { - return const CircularProgressIndicator(); - } - return Row( - children: [ - Text("Query Result: ${query.data}"), - ElevatedButton( - child: const Text("New Id"), - onPressed: () { - setState(() { - id = Random().nextDouble() * 200; - }); - }, - ), - ], - ); - }, - ); - } -} -``` - -Here, everything is same except to pass the `variable-query-key` to the dynamic `Query` you'll have to invoke the defined job (in this case it's `queryVariableKeyJob`) & pass the `variable-query-key` as an argument. \ No newline at end of file diff --git a/docs/docs/basics/LazyQuery.md b/docs/docs/basics/LazyQuery.md deleted file mode 100644 index c5d368e..0000000 --- a/docs/docs/basics/LazyQuery.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Lazy Query -sidebar_position: 7 ---- - -If you ever want to disable a query from automatically running, you can use the enabled = false option in [`QueryJob`](/docs/basics/QueryJob) - -When `enabled` is false: - -- If the query has initial data - - The query will be initialized in the status === 'success' or isSuccess state. -- If the query does not have any data - - The query will start in the status === 'idle' or isIdle state. -- The query will not automatically `fetch` on mount. -- The query will not automatically `refetch` in the background when new instances mount or new instances appearing -- The query will ignore query client `invalidateQueries` and `refetchQueries` calls that would normally result in the query refetching. -- `refetch` can be used to manually trigger the query to fetch - -Here's a basic QueryJob that won't run automatically: - -```dart -final lazyQueryJob = QueryJob( - queryKey: "lazy-query", - enabled: false, - task: (queryKey, data) { - return Future.delayed(const Duration(milliseconds: 500), - () => "Result: key=$queryKey value=$data"); - }, -); -``` - -Let's use this Lazy Query Job in our example: - -```dart - @override - Widget build(BuildContext context) { - return QueryBuilder( - // This query won't run automatically anyway unless the [refetch] method - // is called - job: lazyQueryJob, - externalData: "I can get your heart beat beat beat beating like", - builder: (context, query) { - return Row( - children: [ - Text("Current Data: ${query.data ?? "Loading"}"), - ElevatedButton( - child: const Text("Refetch Query"), - onPressed: () => query.refetch(), - ), - ], - ); - }, - ); - } -``` \ No newline at end of file diff --git a/docs/docs/basics/PaginatedQuery.md b/docs/docs/basics/PaginatedQuery.md deleted file mode 100644 index 5b539ef..0000000 --- a/docs/docs/basics/PaginatedQuery.md +++ /dev/null @@ -1,99 +0,0 @@ ---- -title: Paginated/Lagged Query -sidebar_position: 10 ---- - - -Rendering paginated data is a very common UI pattern and in Fl-Query, it "just works" by including the page information in the query key: - -```dart -final queryVariableKeyJob = QueryJob.withVariableKey( - task: (queryKey, externalData) { - return MyAPI.getData(id: getVariable(queryKey)); - }, -); - -/// inside a widget build method -QueryBuilder( - job: queryVariableKeyJob(id), - externalData: null, - builder: (context, query){...} -) -``` - -However, if you run this simple example, you might notice something strange: - -**The UI jumps in and out of the `success` and `loading` states because each new page is treated like a brand new query.** - -This experience is not optimal and unfortunately is how many tools today insist on working. But not Fl-Query! As you may have guessed, Fl-Query comes with an awesome feature called `keepPreviousData` that allows us to get around this. - -## Better Paginated Queries with `keepPreviousData` - -Consider the following example where we would ideally want to increment a pageIndex (or cursor) for a query. If we were to use just `QueryJob.withVariableKey`, **it would still technically work fine**, but the UI would jump in and out of the `success` and `loading` states as different queries are created and destroyed for each page or cursor. By setting `keepPreviousData` to `true` we get a few new things: - -- **The data from the last successful fetch available while new data is being requested, even though the query key has changed**. -- When the new data arrives, the previous `data` is seamlessly swapped to show the new data. -- `isPreviousData` is made available to know what data the query is currently providing you - -```dart -final todoJob = QueryJob.withVariableKey( - preQueryKey: "todo", - task: (queryKey, _) async { - final res = await http.get( - Uri.parse( - "https://jsonplaceholder.typicode.com/todos/${getVariable(queryKey)}"), - ); - return jsonDecode(res.body); - }, - keepPreviousData: true, -); - -class QueryPreviousDataExample extends StatefulWidget { - const QueryPreviousDataExample({Key? key}) : super(key: key); - - @override - State createState() => - _QueryPreviousDataExampleState(); -} - -class _QueryPreviousDataExampleState extends State { - int id = 1; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - QueryBuilder( - job: todoJob(id.toString()), - externalData: null, - builder: (context, query) { - if (query.hasError) return Text(query.error.toString()); - if (!query.hasData) return const CircularProgressIndicator(); - return Text(jsonEncode(query.data ?? {})); - }), - Row( - children: [ - IconButton( - icon: const Icon(Icons.remove), - onPressed: () { - setState(() { - id -= 1; - }); - }, - ), - IconButton( - icon: const Icon(Icons.add), - onPressed: () { - setState(() { - id += 1; - }); - }, - ), - ], - ) - ], - ); - } -} -``` \ No newline at end of file diff --git a/docs/docs/basics/Queries.mdx b/docs/docs/basics/Queries.mdx index 4c2376d..1c0cb8b 100644 --- a/docs/docs/basics/Queries.mdx +++ b/docs/docs/basics/Queries.mdx @@ -124,4 +124,136 @@ await query.setData("new data"); // type of 'data' has to match the DataType :::tip You can learn more about Optimistic Updates in the [Mutation Tutorial](/docs/basics/mutations) -::: \ No newline at end of file +::: + +### Dynamic key + +You can also use dynamic keys with `QueryBuilder` and `useQuery`. With dart's String interpolation, you can pass dynamic keys to the query + + + + +```dart +QueryBuilder( + "todos/$todoId", + () => api.getTodo(todoId), + builder: (context, query) { + /* ... */ + }, +); +``` + + + + +```dart +useQuery( + "todos/$todoId", + () => api.getTodo(todoId), +); +``` + + + + +> For every new `todoId`, a new query will be created and cached separately + +### Lazy Query + +By default queries are executed immediately after they are mounted. But you can also make them lazy by passing `enabled: false` to the `QueryBuilder` or `useQuery` + + + + +```dart +QueryBuilder( + "lazy-todos", + () => api.getTodos(), + enabled: false, + builder: (context, query) { + /* ... */ + }, +); +``` + + + + +```dart +useQuery( + "lazy-todos", + () => api.getTodos(), + enabled: false, +); +``` + + + + +Now these queries won't be executed as soon as they're mounted. Until `Query.refresh()` or `Query.fetch()` is called these will stay in initial state. If `initial` data was passed, it'll be used until the query is refreshed. Same goes for persisting queries + +### Persisting Queries + +Queries can be persisted by passing `jsonConfig` argument to the `QueryBuilder` or `useQuery`. Persisted queries are stored in [hive](https://docs.hivedb.dev/) cache and are available even after the app is restarted + +First make sure your custom data type is json serializable. You can use [json_serializable](https://pub.dev/packages/json_serializable) package to generate `toJson` and `fromJson` methods for your data type + + +```dart +import 'package:json_annotation/json_annotation.dart'; + +part 'todo.g.dart'; + +@JsonSerializable() +class Todo{ + final String id; + final String title; + final bool completed; + + Todo({ + required this.id, + required this.title, + required this.completed, + }); + + factory Todo.fromJson(Map json) => _$TodoFromJson(json); + Map toJson() => _$TodoToJson(this); +} +``` + + + + +```dart +QueryBuilder( + "todos", + () => api.getTodos(), + jsonConfig: JsonConfig( + fromJson: (json) => Todo.fromJson(json), + toJson: (todo) => todo.toJson(), + ), + builder: (context, query) { + /* ... */ + }, +); +``` + + + + +```dart +useQuery( + "todos", + () => api.getTodos(), + jsonConfig: JsonConfig( + fromJson: (json) => Todo.fromJson(json), + toJson: (todo) => todo.toJson(), + ), +); +``` + + + + +Right now due to lack of reflection support and compile time macros we're unable to serialize any data type on the fly. +That's why `JsonConfig` is required. Otherwise, a simple `persistToDisk: true` would have been enough \ No newline at end of file