FluQuery 🚀

Powerful asynchronous state management for Flutter - Inspired by TanStack Query

pub package License: MIT

✨ Features

  • 🔄 Automatic Caching - Data is cached with configurable stale times
  • 🔁 Background Refetching - Stale data is refreshed automatically
  • 📱 Smart Refetching - On window focus, network reconnect, or mount
  • ⏱️ Polling - Built-in interval-based refetching
  • 📄 Infinite Queries - Cursor-based pagination made easy
  • ✏️ Mutations - CRUD operations with cache invalidation
  • Optimistic Updates - Instant UI with automatic rollback
  • 🏎️ Race Condition Handling - Automatic cancellation of stale requests
  • 💾 Persistence - Save query data to disk
  • 🧩 Services - Built-in dependency injection with lifecycle management
  • 🪝 Hooks API - Beautiful Flutter Hooks integration
  • 🔍 Devtools - Visual debugging tool for inspecting queries and cache

📦 Installation

dependencies:
  fluquery: ^1.3.0
  flutter_hooks: ^0.21.3+1

🚀 Quick Start

1. Setup

import 'package:fluquery/fluquery.dart';

void main() {
  runApp(
    QueryClientProvider(
      client: QueryClient(),
      child: MyApp(),
    ),
  );
}

2. Use Queries

class TodoList extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final todos = useQuery<List<Todo>, Object>(
      queryKey: ['todos'],
      queryFn: (_) => fetchTodos(),
    );

    if (todos.isLoading) return CircularProgressIndicator();
    if (todos.isError) return Text('Error: ${todos.error}');

    return ListView(
      children: todos.data!.map((t) => TodoItem(todo: t)).toList(),
    );
  }
}

3. Mutations

final mutation = useMutation<Todo, Object, String, void>(
  mutationFn: (title) => createTodo(title),
  onSuccess: (data, variables, _) {
    client.invalidateQueries(queryKey: ['todos']);
  },
);

// Trigger
mutation.mutate('New Todo');

4. Devtools

QueryClient(
  config: QueryClientConfig(
    enableDevtools: true,
  ),
);

📖 Core Concepts

Query Options

useQuery<List<Post>, Object>(
  queryKey: ['posts'],
  queryFn: (_) => fetchPosts(),
  staleTime: StaleTime(Duration(minutes: 5)),
  cacheTime: CacheTime(Duration(minutes: 10)),
  refetchInterval: Duration(seconds: 30),
  retry: 3,
  enabled: isLoggedIn,
);

Infinite Queries

final posts = useInfiniteQuery<PostsPage, Object, int>(
  queryKey: ['posts'],
  queryFn: (ctx) => fetchPosts(page: ctx.pageParam ?? 1),
  initialPageParam: 1,
  getNextPageParam: (lastPage, _, __, ___) => 
    lastPage.hasMore ? lastPage.nextPage : null,
);

posts.fetchNextPage(); // Load more

Optimistic Updates

useMutation<Todo, Object, Todo, List<Todo>>(
  mutationFn: (todo) => updateTodo(todo),
  onMutate: (todo) {
    final previous = client.getQueryData<List<Todo>>(['todos']);
    client.setQueryData(['todos'], [...previous!, todo]);
    return previous;
  },
  onError: (_, __, previous) => client.setQueryData(['todos'], previous),
  onSettled: (_, __, ___, ____) => client.invalidateQueries(queryKey: ['todos']),
);

Persistence

// Setup
final persister = HiveCePersister();
await persister.init();
final client = QueryClient(persister: persister);
await client.hydrate();

// Use
useQuery<List<Todo>, Object>(
  queryKey: ['todos'],
  queryFn: (_) => fetchTodos(),
  persist: PersistOptions(serializer: TodoListSerializer()),
);

🧩 Services (Dependency Injection)

FluQuery includes a lightweight service layer for managing dependencies with async lifecycle hooks.

Define Services

class AuthService extends Service {
  final TokenStorage _tokens;
  
  AuthService(ServiceRef ref) : _tokens = ref.getSync<TokenStorage>();

  @override
  Future<void> onInit() async {
    await _loadSession();
  }

  @override
  Future<void> onDispose() async {
    await _clearSession();
  }
}

Register & Use

// Register
await client.initServices((container) {
  container.register<TokenStorage>((ref) => TokenStorage());
  container.register<AuthService>((ref) => AuthService(ref));
});

// In widgets
final auth = useService<AuthService>();

// Or programmatically
final auth = await client.getService<AuthService>();

QueryStore in Services

Services can own QueryStore instances that are automatically disposed:

class UserService extends Service {
  late final QueryStore<User?, Object> userStore;

  UserService(ServiceRef ref) {
    userStore = ref.createStore(
      queryKey: ['current-user'],
      queryFn: (_) => fetchCurrentUser(),
    );
  }
}

// In widgets
final store = useServiceStore<UserService, User?, Object>(
  (service) => service.userStore,
);

Factory & Named Services

// Factory - new instance each time
container.registerFactory<Logger>((ref) => Logger());
final logger = container.create<Logger>();

// Named - multiple instances of same type
container.registerNamed<ApiClient>('v1', (ref) => ApiClient('api.v1'));
container.registerNamed<ApiClient>('v2', (ref) => ApiClient('api.v2'));
final v1 = await container.get<ApiClient>(name: 'v1');

⚙️ Configuration

QueryClient(
  config: QueryClientConfig(
    defaultOptions: DefaultQueryOptions(
      staleTime: StaleTime(Duration(minutes: 5)),
      cacheTime: CacheTime(Duration(minutes: 10)),
      retry: 3,
      refetchOnWindowFocus: true,
    ),
    logLevel: LogLevel.warn,
  ),
);

🎯 API Reference

Hooks

Hook Description
useQuery Fetch and cache data
useQuerySelect Fetch with data transformation
useMutation Create/update/delete operations
useInfiniteQuery Paginated queries
useQueries Parallel queries
useQueryClient Access QueryClient
useService Access a service
useServiceStore Access a service's QueryStore

QueryResult

Property Description
data The resolved data
error Error if any
isLoading Initial load
isFetching Any fetch in progress
isError / isSuccess State checks
refetch() Manual refetch

QueryClient Methods

Method Description
fetchQuery Fetch programmatically
getQueryData / setQueryData Direct cache access
invalidateQueries Mark stale & refetch
cancelQueries Cancel in-flight
getService Get a service instance

📱 Example App

# Start backend
cd backend && dart pub get && dart run bin/server.dart

# Run app
cd example && flutter run

🤝 Contributing

Contributions welcome! Please open an issue or PR.

📄 License

MIT License - see LICENSE


Made with ❤️ for the Flutter community

Libraries

fluquery
FluQuery - Powerful asynchronous state management for Flutter