Skip to Content
DocumentationCore PackageEssentialsQueryBuilder

QueryBuilder

The QueryBuilder widget is the heart of fetching data in Fasq. It turns asynchronous data sources into reactive UI states.

Usage

import 'package:fasq/fasq.dart'; QueryBuilder<List<Todo>>( queryKey: 'todos'.toQueryKey(), queryFn: () => fetchTodos(), builder: (context, state) { // 1. Loading State if (state.isLoading) { return const CircularProgressIndicator(); } // 2. Error State if (state.hasError) { return Text('Error: ${state.error}'); } // 3. Success State return ListView( children: state.data!.map((todo) => Text(todo.title)).toList(), ); }, )

API

Parameters

NameTypeRequiredDescription
queryKeyQueryKeyYesUnique identifier for caching and sharing the query. Use 'key'.toQueryKey().
queryFnFuture<T> Function()NoThe function that fetches the data. Required if queryFnWithToken is not provided.
queryFnWithTokenFuture<T> Function(CancellationToken)NoFunction that fetches data with a cancellation token. Required if queryFn is not provided.
builderWidget Function(BuildContext, QueryState<T>)YesBuilds the UI based on the current state.
optionsQueryOptions?NoConfiguration for caching, refetching, and side effects.
dependsOnQueryKey?NoKey of a parent query. If the parent is disposed, this query is cancelled.

QueryState Properties

The state object passed to the builder contains:

PropertyTypeDescription
dataT?The data returned from the query function. null if not loaded or errored.
errorObject?The error object if the query failed.
statusQueryStatusCurrent status: loading, success, or error.
isLoadingboolTrue if the query is performing the initial fetch (hard loading).
isFetchingboolTrue if the query is fetching, including background refetches.
hasDataboolTrue if data is not null.
hasErrorboolTrue if error is not null.

Examples

Customizing Configuration (Options)

You can pass QueryOptions to customize how the query behaves.

QueryBuilder<User>( queryKey: 'user:1'.toQueryKey(), queryFn: () => fetchUser(1), options: const QueryOptions( staleTime: Duration(minutes: 5), // Data remains fresh for 5 mins refetchOnResume: true, // Refetch when app comes to foreground ), builder: (context, state) { if (state.hasData) return UserProfile(state.data!); return const LoadingSpinner(); }, )

Background Refetching Indicators

Distinguish between initial loading and background updates.

QueryBuilder<List<Post>>( queryKey: 'feed'.toQueryKey(), queryFn: () => fetchFeed(), builder: (context, state) { if (state.isLoading) return const ShimmerList(); return Column( children: [ // Show a small loader when refreshing in background if (state.isFetching) const LinearProgressIndicator(), Expanded( child: ListView.builder( itemCount: state.data?.length ?? 0, itemBuilder: (_, i) => PostTile(state.data![i]), ), ), ], ); }, )

Dependent Queries

Queries can depend on variables. FASQ handles key changes automatically.

class UserPosts extends StatelessWidget { final String userId; const UserPosts({required this.userId}); @override Widget build(BuildContext context) { return QueryBuilder<List<Post>>( // The key includes the userId. If userId changes, // Fasq automatically fetches data for the new user. queryKey: 'posts:$userId'.toQueryKey(), queryFn: () => fetchPostsForUser(userId), builder: (context, state) { // ... build UI }, ); } }

Request Cancellation

Fasq supports cancelling in-flight requests when a query is disposed or re-fetched. use queryFnWithToken to access the cancellation token.

/// Example using Dio QueryBuilder<List<Todo>>( queryKey: 'todos'.toQueryKey(), // Use queryFnWithToken instead of queryFn queryFnWithToken: (token) async { final cancelToken = CancelToken(); // Register cancellation callback token.onCancel(() => cancelToken.cancel()); try { final response = await dio.get( '/todos', cancelToken: cancelToken, ); return (response.data as List).map((e) => Todo.fromJson(e)).toList(); } on DioException catch (e) { // Fasq handles cancellations silently if they throw exceptions // matching your client's cancellation error type. // If you rethrow properly, Fasq will recognize it. rethrow; } }, builder: (context, state) { if (state.isLoading) return const CircularProgressIndicator(); return TodoList(state.data!); }, ) /// Example using http package QueryBuilder<String>( queryKey: 'data'.toQueryKey(), queryFnWithToken: (token) async { final client = http.Client(); // Close client on cancellation token.onCancel(() => client.close()); try { final response = await client.get(Uri.parse('https://example.com')); return response.body; } finally { // Clean up if not cancelled via token if (!token.isCancelled) { client.close(); } } }, builder: (context, state) => Text(state.data ?? 'Loading...'), )
Last updated on