QueryBuilder
The QueryBuilder widget is the heart of fetching data in Fasq. It turns asynchronous data sources into reactive UI states.
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(), ); },)Parameters
Section titled “Parameters”| Name | Type | Required | Description |
|---|---|---|---|
queryKey | QueryKey | Yes | Unique identifier for caching and sharing the query. Use 'key'.toQueryKey(). |
queryFn | Future<T> Function() | No | The function that fetches the data. Required if queryFnWithToken is not provided. |
queryFnWithToken | Future<T> Function(CancellationToken) | No | Function that fetches data with a cancellation token. Required if queryFn is not provided. |
builder | Widget Function(BuildContext, QueryState<T>) | Yes | Builds the UI based on the current state. |
options | QueryOptions? | No | Configuration for caching, refetching, and side effects. |
dependsOn | QueryKey? | No | Key of a parent query. If the parent is disposed, this query is cancelled. |
QueryState Properties
Section titled “QueryState Properties”The state object passed to the builder contains:
| Property | Type | Description |
|---|---|---|
data | T? | The data returned from the query function. null if not loaded or errored. |
error | Object? | The error object if the query failed. |
status | QueryStatus | Current status: loading, success, or error. |
isLoading | bool | True if the query is performing the initial fetch (hard loading). |
isFetching | bool | True if the query is fetching, including background refetches. |
hasData | bool | True if data is not null. |
hasError | bool | True if error is not null. |
Examples
Section titled “Examples”Customizing Configuration (Options)
Section titled “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
Section titled “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
Section titled “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
Section titled “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 DioQueryBuilder<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 packageQueryBuilder<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...'),)