Skip to content

Error Handling

Fasq provides robust error handling to ensure your app recovers gracefully from network failures or exceptions.

Errors are exposed in the QueryState.

QueryBuilder<User>(
queryKey: 'user'.toQueryKey(),
queryFn: () => throw Exception('Network failed'),
builder: (context, state) {
if (state.hasError) {
return Text('Error: ${state.error}');
}
return Text('Data: ${state.data}');
},
)
OptionTypeDescription
retryintNumber of times to retry failed requests. Default is 3.
retryDelayDurationDelay between retries. Default is 1s.
onErrorFunction(err)Callback executed when a query fails (after retries).

Retry failed requests automatically to handle flaky networks.

QueryOptions(
retry: 3,
retryDelay: Duration(seconds: 2), // Wait 2s between attempts
)

Show a snackbar whenever any query fails.

// Using QueryClientObserver
class GlobalErrorHandler implements QueryClientObserver {
@override
void onQueryError(QuerySnapshot snapshot, QueryMeta? meta, BuildContext? context) {
showToast('Something went wrong: ${snapshot.currentState.error}');
}
// Implement other required methods...
}
// Register it
client.addObserver(GlobalErrorHandler());

For development debugging, use FasqLogger which automatically logs all errors:

client.addObserver(FasqLogger(
enabled: true,
showData: false,
));

This will log errors in the format: ❌ [Error] query-key: Error details

For complete logging documentation, see Logging.

Give users a way to try again.

if (state.hasError) {
return Column(
children: [
Text('Failed to load'),
ElevatedButton(
// Invalidating triggers a refetch
onPressed: () => QueryClient().invalidateQuery('my-key'.toQueryKey()),
child: Text('Retry'),
)
],
);
}

Fasq includes built-in circuit breaker functionality to protect your application from cascading failures. When a service is experiencing issues, the circuit breaker automatically stops sending requests to prevent resource exhaustion.

QueryBuilder<User>(
queryKey: 'user'.toQueryKey(),
queryFn: () => api.fetchUser(),
options: QueryOptions(
circuitBreaker: CircuitBreakerOptions(
failureThreshold: 5,
resetTimeout: Duration(seconds: 60),
),
),
builder: (context, state) {
if (state.hasError && state.error is CircuitBreakerOpenException) {
return Text('Service temporarily unavailable');
}
// ...
},
)

When the circuit breaker opens, requests are immediately rejected with a CircuitBreakerOpenException, allowing you to handle the failure gracefully (e.g., show cached data or a fallback UI).

For complete configuration details, state machine behavior, and best practices, see the Circuit Breaker documentation.

FASQ includes a comprehensive error tracking system that captures rich context when queries fail, making it easy to diagnose production issues and integrate with external error reporting services.

Integrate with Sentry, Crashlytics, or any error tracking service in just a few lines:

class SentryErrorReporter implements FasqErrorReporter {
@override
void report(FasqErrorContext context) {
Sentry.captureException(
context.error,
stackTrace: context.stackTrace,
hint: Hint.withMap({
'queryKey': context.queryKey.join('/'),
'retryCount': context.retryCount,
'networkStatus': context.networkStatus ? 'online' : 'offline',
}),
);
}
}
// Register it
final client = QueryClient();
client.addErrorReporter(SentryErrorReporter());

When a query fails, FASQ automatically captures:

  • Query Key: Which query failed
  • Retry Count: How many retries were attempted
  • Network Status: Whether the device was online
  • Error & Stack Trace: The actual error that occurred
  • Sanitized Options: Safe query configuration (PII automatically removed)

FASQ automatically sanitizes query options to prevent sensitive data leaks. Sensitive fields like callbacks, user-specific metadata, and authentication tokens are excluded from error reports by default.

For complete documentation on error tracking, including PII sanitization details, integration examples, and best practices, see Error Tracking.