Recipes

Cancellation

Use CancelToken to abort in-flight fetches cooperatively: on navigation, timeout, or user action.

CancelToken is a one-shot cooperative cancellation signal. Pass it to fetchQuery, watchQuery, or prefetch and call cancel() to abort the fetch before start or during execution.


Creating a Token

main.dart
final token = CancelToken();

A token can only be cancelled once. Calling cancel() a second time is a no-op.


Cancelling a fetchQuery

user_repository.dart
final token = CancelToken();

// Start the fetch
final future = client.fetchQuery<User>(
  key: ['users', userId],
  fetcher: () => api.getUser(userId),
  cancelToken: token,
);

// Cancel it (e.g. the user navigated away)
token.cancel();

try {
  final user = await future;
} on QoraCancelException catch (e) {
  // e.key is the string-encoded query key
  print('Fetch cancelled: ${e.key}');
}

When cancelled, fetchQuery throws QoraCancelException to the caller and restores the cache entry to its pre-fetch snapshot (no partial state is left behind).


Cancelling a watchQuery stream

user_page.dart
final token = CancelToken();

final sub = client.watchQuery<User>(
  key: ['users', userId],
  fetcher: () => api.getUser(userId),
  cancelToken: token,
).listen((state) { ... });

// On widget dispose: cancel the in-flight fetch
token.cancel();
await sub.cancel();
Cancelling the stream subscription is separate from cancelling the fetch. token.cancel() aborts the HTTP request; sub.cancel() removes the state listener. Do both on dispose for a clean teardown.

Timeout Pattern

Combine CancelToken with Future.delayed to implement a request timeout:

user_repository.dart
Future<User> fetchWithTimeout(int userId, Duration timeout) async {
  final token = CancelToken();

  Timer(timeout, token.cancel);

  try {
    return await client.fetchQuery<User>(
      key: ['users', userId],
      fetcher: () => api.getUser(userId),
      cancelToken: token,
    );
  } on QoraCancelException {
    throw TimeoutException('User fetch timed out after $timeout');
  }
}

In a StatefulWidget, tie the token to the widget lifecycle:

user_screen.dart
class _UserScreenState extends State<UserScreen> {
  CancelToken? _fetchToken;

  Future<void> _loadUser() async {
    _fetchToken = CancelToken();
    try {
      await context.qora.fetchQuery<User>(
        key: ['users', widget.userId],
        fetcher: () => api.getUser(widget.userId),
        cancelToken: _fetchToken,
      );
    } on QoraCancelException {
      // Widget disposed: ignore
    }
  }

  @override
  void dispose() {
    _fetchToken?.cancel();
    super.dispose();
  }
}

CancelToken API

Constructor

CancelToken()

Properties

PropertyTypeDescription
isCancelledbooltrue after cancel() is called.

Methods

MethodSignatureDescription
cancel()voidMark the token as cancelled. Idempotent and safe to call multiple times.

QoraCancelException

Thrown by fetchQuery and prefetch when a fetch is cancelled via CancelToken.

class QoraCancelException implements Exception {
  final String key; // JSON-encoded normalised query key
}
PropertyTypeDescription
keyStringThe string-encoded query key, e.g. '["users",1]'

Tracker Hook

QoraTracker.onQueryCancelled(String key) is called every time a fetch is cancelled via CancelToken. Implement it on your custom tracker to log or measure cancellation rates:

analytics_tracker.dart
class AnalyticsTracker extends NoOpTracker {
  @override
  void onQueryCancelled(String key) {
    analytics.track('query_cancelled', {'key': key});
  }
}

Copyright © 2026