Skip to main content

queryx_riverpod 0.1.1 copy "queryx_riverpod: ^0.1.1" to clipboard
queryx_riverpod: ^0.1.1 copied to clipboard

Riverpod bindings for QueryX — reactive queries, mutations, and infinite pagination with automatic widget rebuilds.

QueryX Riverpod

Riverpod bindings for QueryX with reactive providers and zero manual listener wiring.

pub.dev version pub.dev likes CI Riverpod

Query, mutation, and infinite-query bindings for Riverpod — automatic rebuilds, shared caching, deduplication, retry, and invalidation through QueryX.


queryx_riverpod #

Riverpod bindings for queryx — Query, Mutation, and InfiniteQuery exposed as StateNotifierProviders, so widgets get automatic rebuilds with zero manual listener wiring.

This package is a thin adapter, not a second engine: every query still goes through one shared QueryClient, so deduplication, caching, retry, and invalidation all work exactly like they do in plain queryx — Riverpod just gets to watch() the result.

Setup #

void main() {
  final queryClient = QueryClient(
    defaultStaleTime: const Duration(minutes: 5),
  );

  runApp(
    ProviderScope(
      overrides: [queryxClientProvider.overrideWithValue(queryClient)],
      child: const MyApp(),
    ),
  );
}

queryxClientProvider throws if you forget to override it — this is intentional: constructing a throwaway client per-provider would silently break cross-widget cache sharing, the entire point of queryx.

Queries #

final usersProvider = queryProvider<List<User>>(
  QueryKey(['users']),
  (ref) => () => ref.read(apiProvider).getUsers(),
);

class UsersScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final users = ref.watch(usersProvider);
    return users.when(
      loading: () => const CircularProgressIndicator(),
      error: (e) => Text(e.message),
      data: (data) => ListView(children: [for (final u in data) Text(u.name)]),
    );
  }
}

queryProvider is .autoDispose: when the last widget watching it unmounts, the underlying Query observer is released. If another widget elsewhere still has the same key open, its data is untouched; otherwise queryx's own cacheTime GC takes it from there. Riverpod's lifecycle and queryx's cache lifecycle compose for free.

Dependent / parameterized queries #

queryProvider covers the common case (one query, no runtime argument). For a query that depends on an argument — e.g. ['user', userId] — use Riverpod's own .family directly with QueryNotifier:

final userProvider = StateNotifierProvider.autoDispose
    .family<QueryNotifier<User>, QueryxSnapshot<User>, int>((ref, userId) {
  final client = ref.watch(queryxClientProvider);
  final query = client.query<User>(
    QueryKey(['user', userId]),
    () => ref.read(apiProvider).getUser(userId),
  );
  return QueryNotifier<User>(query);
});

// usage: ref.watch(userProvider(42))

For a query that should only run once another query has data (spec's "dependent queries"), pass enabled based on that other provider's state:

final ordersProvider = queryProvider<List<Order>>(
  QueryKey(['orders', userId]),
  (ref) => () => api.getOrders(userId),
  options: QueryOptions(enabled: ref.watch(userProvider(userId)).hasData),
);

Mutations #

final likePostProvider = mutationProvider<int, void, int>(
  (ref) => (_) => api.post('/posts/1/like'),
  optionsBuilder: (ref) {
    final client = ref.read(queryxClientProvider);
    return MutationOptions(
      onMutate: (_) async {
        final snapshot = client.getQueryData<int>(likesKey)!;
        client.updateQueryData<int>(likesKey, (n) => (n ?? 0) + 1); // instant UI update
        return snapshot;
      },
      onSuccess: (serverCount, _, __) => client.setQueryData(likesKey, serverCount),
      onError: (error, _, snapshot) => client.setQueryData(likesKey, snapshot!), // rollback
    );
  },
);

ElevatedButton(
  onPressed: () => ref.read(likePostProvider.notifier).mutate(null),
  child: const Icon(Icons.favorite),
)

optionsBuilder (not a plain options value) is what it is because onMutate/onSuccess/onError almost always need ref — usually to reach queryxClientProvider for cache writes or client.invalidateQueries(...).

Infinite queries #

final feedProvider = infiniteQueryProvider<Post, int>(
  QueryKey(['posts']),
  initialPageParam: 0,
  fetchPageBuilder: (ref) => (page) => ref.read(apiProvider).getPosts(page: page ?? 0),
);

final feed = ref.watch(feedProvider);
// feed.items, feed.hasNextPage, feed.isFetchingNextPage
ref.read(feedProvider.notifier).fetchNextPage();

Why this depends on riverpod, not flutter_riverpod #

The binding logic here (QueryNotifier, MutationNotifier, InfiniteQueryNotifier) only needs StateNotifier and Ref — both live in the pure-Dart riverpod package. That keeps this package unit-testable with plain dart test (see test/), with no Flutter test harness required. Inside a real Flutter app, flutter_riverpod's WidgetRef/ConsumerWidget are built directly on top of riverpod's Ref/ProviderContainer, so nothing here needs to change to be consumed from widgets — see example/lib/main.dart for a full Flutter app using it.

Installation #

Add queryx_riverpod and flutter_riverpod to your Flutter app:

flutter_riverpod is only needed for Flutter UI integration, such as ConsumerWidget, ConsumerStatefulWidget, WidgetRef, and ProviderScope.

dependencies:
  queryx_riverpod: ^0.1.0
  flutter_riverpod: ^2.6.1 # for ConsumerWidget/WidgetRef in your app
1
likes
150
points
87
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Riverpod bindings for QueryX — reactive queries, mutations, and infinite pagination with automatic widget rebuilds.

Homepage
Repository (GitHub)
View/report issues

Topics

#riverpod #networking #caching #state-management

License

MIT (license)

Dependencies

queryx, riverpod

More

Packages that depend on queryx_riverpod