volt 1.0.0 copy "volt: ^1.0.0" to clipboard
volt: ^1.0.0 copied to clipboard

Simple, fast, effortless data fetching and real-time updates

volt ⚡️ #

Effortlessly manage asynchronous data fetching, caching, and real-time data delivery with minimal code.

Features #

  • ⚡️ Minimal boilerplate code for faster development
  • 📡 Reactive updates for data consistency between components
  • 🚀 In-memory caching for instant data access
  • 💾 Disk caching with offline support
  • 🔄 Query deduplication to reduce network requests
  • 🔮 Configurable auto-refetching for fresh data
  • 🧩 Easy integration with Flutter projects
  • 🧠 Compute isolate support for heavy operations
  • 📦 Lightweight package with minimal dependencies
  • 🔒 Error handling with automatic retry mechanisms

Install #

flutter pub add volt

Usage #

Query

VoltQuery<Photo> photoQuery(String id) => VoltQuery(
      queryKey: ['photo', id],
      queryFn: () => fetch('https://jsonplaceholder.typicode.com/photos/$id'),
      select: Photo.fromJson,
    );

Widget build(BuildContext context) {
  final photo = useQuery(photoQuery('1'));

  return photo == null ? CircularProgressIndicator() : Text('Photo: ${photo.title}');
}

Mutation

VoltMutation<String> useDeletePhotoMutation() {
  final queryClient = useQueryClient();

  return useMutation(
    mutationFn: (photoId) => fetch(
      'https://jsonplaceholder.typicode.com/photos/$photoId',
      method: 'DELETE',
    ),
    onSuccess: (photoId) => queryClient.prefetchQuery(photoQuery(photoId)),
  );
}

Widget build(BuildContext context) {
  final deletePhotoMutation = useDeletePhotoMutation();

  return deletePhotoMutation.state.isLoading
      ? const CircularProgressIndicator()
      : ElevatedButton(
          onPressed: () => deletePhotoMutation.mutate('1'),
          child: const Text('Delete Photo'),
        );
}

Configuration

Widget build(BuildContext context) {
  final queryClient = useMemoized(() => QueryClient(
    // Transforms query keys (useful for cache segmentation by environment/locale)
    keyTransformer: (keys) => keys,

    // Custom persistor for memory and disk caching
    persistor: FileVoltPersistor(),

    // Global default stale duration
    staleDuration: const Duration(hours: 1),

    // Enable debug mode for extra logging and stats
    isDebug: false,

    // Listener for query events (cache hits, network errors, etc.)
    listener: null,
  ));

  return QueryClientProvider(
    client: queryClient,
    child: MyApp(),
  );
}

Query dependencies

A null queryFn acts the same as enabled: false

final accountQuery = VoltQuery(
  queryKey: ['account'],
  queryFn: () async => fetch('https://jsonplaceholder.typicode.com/account/1'),
  select: Account.fromJson,
);

VoltQuery<Photos> photosQuery(Account? account) =>
    VoltQuery(
      queryKey: ['photos', account?.id],
      queryFn: account == null
          ? null
          : () async => fetch('https://jsonplaceholder.typicode.com/account/${account.id}/photos/'),
      select: Photos.fromJson,
    );

Widget build(BuildContext context) {
  final account = useQuery(accountQuery);
  final photos = useQuery(photosQuery(account));

  ...
}

Best Practices #

Response Object Equality #

Response objects should implement equality to ensure proper change detection and prevent unnecessary rebuilds. Use equatable, freezed, or implement equality manually.

Query Key Structure #

Use consistent, hierarchical query keys. Start with a general identifier and add specifics:

// Good
['users']
['users', userId]
['users', userId, 'posts']
['users', userId, 'posts', postId]

// Avoid
['getUser123']
[userId, 'users']  // inconsistent order

Extract Query Definitions #

Define queries as functions outside widgets for reusability and testability:

// Good - reusable across the app
VoltQuery<User> userQuery(String id) => VoltQuery(
  queryKey: ['user', id],
  queryFn: () => fetchUser(id),
  select: User.fromJson,
);

// Avoid - inline queries are harder to reuse
useQuery(VoltQuery(queryKey: ['user', id], ...));

Cache segmentation #

Use keyTransformer in QueryClient to automatically segment cache by environment (production, staging, etc.)/locale (en, es, etc.) for all queries:

final queryClient = QueryClient(
  keyTransformer: (keys) => [
    isProduction ? 'production' : 'staging',
    locale,
    ...keys
    ],
);

This ensures cache isolation between environments and prevents data conflicts.

Persistence #

By default, Volt persists data to disk using the FileVoltPersistor. Which relies on no heavy dependencies and is very fast (uses the file system). Although, this can be overridden with a custom persister in the QueryClient constructor.

Credits #

Volt's public API design was inspired by React Query, a popular data-fetching and state management library for React applications.

Special thanks to flutter_hooks for bringing React-style hooks to Flutter, which made this package possible.

5
likes
130
points
347
downloads

Publisher

unverified uploader

Weekly Downloads

Simple, fast, effortless data fetching and real-time updates

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

collection, crypto, disk_space_2, flutter, flutter_hooks, path_provider, rxdart, synchronized

More

Packages that depend on volt