Skip to content

Riverpod Adapter

The Riverpod adapter provides idiomatic, type-safe providers for Fasq using flutter_riverpod. It simplifies state management by integrating Fasq’s powerful caching and synchronization with Riverpod’s dependency injection and reactive patterns.

While Riverpod is excellent for managing application state, it doesn’t natively handle the complexities of “Server State” (data that lives on a server, is asynchronous, and needs caching). Fasq solves several key problems for Riverpod users:

  • Professional Caching: Implements Stale-While-Revalidate (SWR), background refetching, and custom cache expiry (staleTime) natively.
  • Simplified Mutations: Provides a dedicated mutationProvider with built-in loading/error states and an imperative API (.mutate()).
  • Offline Sync: Automatically queues mutations while offline and syncs them once the connection is restored.
  • Infinite Pagination: High-level abstractions for infinite scrolling and cursor-based pagination that reduce boilerplate.
  • Coordinated Invalidation: Easily invalidate specific queries or entire “tags” of data to keep your UI in sync after updates.
  • Declarative Retries & Polling: Configure retry logic and polling intervals as simple declarations rather than manual timers.
  • Standard Riverpod Experience: Returns AsyncValue<T>, so it feels exactly like a standard Riverpod provider but with much more power.

The Riverpod adapter provides:

  • queryProvider - Creates queries that return AsyncValue<T>
  • infiniteQueryProvider - Handles paginated data with AsyncValue
  • mutationProvider - Manages server-side operations with imperative calls
  • PrefetchExtension - Extension on WidgetRef for easy prefetching
  • Full Dependency Injection - Uses fasqClientProvider for the underlying client

Add the adapter and Riverpod to your project:

Terminal window
flutter pub add fasq_riverpod flutter_riverpod

[!NOTE] This adapter is built on top of the fasq core package, which will be added automatically as a dependency.

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fasq_riverpod/fasq_riverpod.dart';
// 1. Define your provider
final usersProvider = queryProvider<List<User>>(
'users'.toQueryKey(),
() => api.fetchUsers(),
options: QueryOptions(
staleTime: Duration(minutes: 5),
),
);
class UsersScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
// 2. Watch the provider
final usersAsync = ref.watch(usersProvider);
// 3. Handle states using .when()
return usersAsync.when(
data: (users) => ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) => ListTile(
title: Text(users[index].name),
),
),
loading: () => Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
);
}
}

Fasq Riverpod providers return Riverpod’s native AsyncValue<T>. This means you can use all the familiar patterns like .when(), .maybeWhen(), and AsyncValue.copyWithPrevious.

Everything is strictly typed. If your fetch function returns Future<User>, your provider will be of type AutoDisposeAsyncNotifierProvider<..., User>, and ref.watch will return AsyncValue<User>.

The providers automatically handle background refetching. When data is being updated in the background, AsyncValue preserves the previous data so your UI doesn’t flicker, while isLoading (or isRefreshing in Riverpod 2) correctly indicates the network activity.

Use the provider’s notifier to trigger a manual refetch:

ElevatedButton(
onPressed: () => ref.read(usersProvider.notifier).refetch(),
child: Text('Refresh'),
)

Mutations are triggered via the notifier’s mutate method:

final createUserProvider = mutationProvider<User, String>(
(name) => api.createUser(name),
);
// In your widget:
final mutation = ref.watch(createUserProvider);
ElevatedButton(
onPressed: mutation.isLoading
? null
: () => ref.read(createUserProvider.notifier).mutate('John Doe'),
child: Text('Create User'),
)