volt 1.0.0
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.