Skip to main content

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

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!);
},
);
}
}

Both QueryBuilder and useQuery accepts error and success callbacks as well as configurations such as retry, statleDuration, cacheDuration etc.

API

Argument nameTypeDescription
queryKey* (unnamed)StringThe unique identifier of the query
queryFn* (unnamed)VoidCallbackAsync Function that is used for getting resources
initialData<DataType>Initial data before query is fetched
onDataValueChanged<DataType>Callback that supplies data when query is successfully fetched/refreshed
onErrorValueChanged<ErrorType>Callback fired when query encounters an error while fetching
jsonConfigJsonConfig<DataType>Supply fromJson and toJson for your returned data in onData
If it's null, query won't be persisted
enabledboolDefines if query should be fetched immediately on mount or not
retryConfigRetryConfigConfigure retry behvaiour
refreshConfigRefreshConfigConfigure 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 passed initialData is being used and the query hasn't been fetched yet
    • isLoading: When the task function is running
    • isRefreshing: When new data is being fetched or simply the refresh method is executing
    • isInactive: When a query isn't used by any Widget (query isn't mounted)
  • Data availability status of Query
    • hasData: When query contains data
    • hasError: 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

info

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
tip

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

QueryBuilder<String, HttpException>(
"todos/$todoId",
() => api.getTodo(todoId),
builder: (context, query) {
/* ... */
},
);

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

QueryBuilder<String, HttpException>(
"lazy-todos",
() => api.getTodos(),
enabled: false,
builder: (context, query) {
/* ... */
},
);

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