Queries
Create a Query
Most of the time you'll be using QueryBuilder
/useQuery
to create & manipulate Queries.
QueryBuilder
just like any other Builder
widget in Flutter. It has 2 required (unnamed) parameters queryKey
and queryFn
and 1 required named
parameter builder
On the other hand, useQuery
accepts arguments just as same as QueryBuilder
but it returns a Query
instead of a Widget
and doesn't have the builder
argument
- Vanilla
- Flutter Hooks
class Example extends StatelessWidget {
const Example({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return QueryBuilder<String, HttpException>(
"todos",
() => api.getTodos(),
builder: (context, query) {
if (query.isLoading) {
return const CircularProgressIndicator();
}
return Text(query.data!);
},
);
}
}
class Example extends HookWidget {
const Example({Key? key}) : super(key: key);
Widget build(BuildContext context) {
final query = useQuery<String, HttpException>(
"todos",
() => api.getTodos(),
);
if (query.isLoading) {
return const CircularProgressIndicator();
}
return Text(query.data!);
}
}
Both QueryBuilder
and useQuery
accepts error and success callbacks as well as configurations such as retry, statleDuration
, cacheDuration
etc.
API
Argument name | Type | Description |
---|---|---|
queryKey* (unnamed) | String | The unique identifier of the query |
queryFn* (unnamed) | VoidCallback | Async Function that is used for getting resources |
initialData | <DataType> | Initial data before query is fetched |
onData | ValueChanged<DataType> | Callback that supplies data when query is successfully fetched/refreshed |
onError | ValueChanged<ErrorType> | Callback fired when query encounters an error while fetching |
jsonConfig | JsonConfig<DataType> | Supply fromJson and toJson for your returned data in onDataIf it's null, query won't be persisted |
enabled | bool | Defines if query should be fetched immediately on mount or not |
retryConfig | RetryConfig | Configure retry behvaiour |
refreshConfig | RefreshConfig | Configure refresh behvaiour |
Query
The Query
is passed to the builder
callback or returned by useQuery
The query
parameter aka Query
contains all the useful getters, properties & methods for rendering data from the query. It contains the state of the current query, the data, the error, the loading status etc along with useful methods such as refresh
and setData
Statuses
The query
contains the status of the current query. It has 2 types of status: 1. Query Progression status 2. Data availability status
You can access them as follows:
- Progressive status of Query
isInitial
: When the passedinitialData
is being used and the query hasn't been fetched yetisLoading
: When the task function is runningisRefreshing
: When new data is being fetched or simply therefresh
method is executingisInactive
: When a query isn't used by any Widget (query isn't mounted)
- Data availability status of Query
hasData
: When query contains datahasError
: When the query contains error
Now the most important part of query: Data and Error. You can access the data returned from the task using query.data
or the error query.error
. Both can be null. So always check if the data/error is null before accessing it
Don't use only query.isLoading
to check if the data is available or not as the query can be failed & at this time data
which can cause UI Exceptions. So use query.hasData
always to check if data
is available yet or not or use both together
Refresh
Another important part is Query.refresh
. You can use it to manually trigger refresh or force the query to get the latest data
await query.refresh();
Set data Manually
Finally, you can use setData
to manually set the data of the query. This is useful when you want to refresh the query but the newest data is already available in the application. It can be used to reduce network traffic by saving network calls to the server. Or you can use it with Mutations
to optimistically set data before the Mutation is executed & then update the query with actual data
await query.setData("new data"); // type of 'data' has to match the DataType
You can learn more about Optimistic Updates in the Mutation Tutorial
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
- Vanilla
- Flutter Hooks
QueryBuilder<String, HttpException>(
"todos/$todoId",
() => api.getTodo(todoId),
builder: (context, query) {
/* ... */
},
);
useQuery<String, HttpException>(
"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
- Vanilla
- Flutter Hooks
QueryBuilder<String, HttpException>(
"lazy-todos",
() => api.getTodos(),
enabled: false,
builder: (context, query) {
/* ... */
},
);
useQuery<String, HttpException>(
"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