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.flutter-io.cn version pub.flutter-io.cn 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 queryxQuery, 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
150
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