flutter_query_plus 0.0.6 copy "flutter_query_plus: ^0.0.6" to clipboard
flutter_query_plus: ^0.0.6 copied to clipboard

A powerful, React Query-inspired data fetching and server state management library for Flutter. Supports caching, retries, infinite queries, and mutations.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_query_plus/flutter_query_plus.dart';

void main() {
  runApp(
    // 1. Initialize and Provide the QueryClient to the entire widget tree.
    QueryProvider(
      client: QueryClient(),
      // 2. Add QueryDevtoolsOverlay to inspect queries during development
      child: const QueryDevtoolsOverlay(
        child: FlutterQueryDemoApp(),
      ),
    ),
  );
}

class FlutterQueryDemoApp extends StatelessWidget {
  const FlutterQueryDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Query Demo',
      theme: ThemeData(
        colorSchemeSeed: Colors.deepPurple,
        useMaterial3: true,
      ),
      home: const MainScreen(),
    );
  }
}

class MainScreen extends HookWidget {
  const MainScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final selectedIndex = useState(0);

    return Scaffold(
      body: IndexedStack(
        index: selectedIndex.value,
        children: const [
          BasicQueryView(),
          InfiniteQueryView(),
        ],
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: selectedIndex.value,
        onDestinationSelected: (idx) => selectedIndex.value = idx,
        destinations: const [
          NavigationDestination(
            icon: Icon(Icons.list),
            label: 'Basic Query',
          ),
          NavigationDestination(
            icon: Icon(Icons.all_inclusive),
            label: 'Infinite List',
          ),
        ],
      ),
    );
  }
}

// -----------------------------------------------------------------------------
// MOCK APIS
// -----------------------------------------------------------------------------

Future<List<String>> fetchProducts() async {
  await Future.delayed(const Duration(seconds: 1)); // Simulate latency
  return ['Mock Macbook', 'Mock iPhone', 'Mock AirPods', 'Mock iPad'];
}

Future<void> addProductApi(String product) async {
  await Future.delayed(const Duration(seconds: 1)); // Simulate mutation
  // In a real app we'd save it to the DB.
}

Future<List<String>> fetchPage(int? page) async {
  await Future.delayed(const Duration(seconds: 1));
  final p = page ?? 1; // Default to page 1
  return List.generate(15, (i) => 'Item ${(p - 1) * 15 + i + 1}');
}

// -----------------------------------------------------------------------------
// BASIC QUERY & MUTATION EXAMPLES
// -----------------------------------------------------------------------------

class BasicQueryView extends HookWidget {
  const BasicQueryView({super.key});

  @override
  Widget build(BuildContext context) {
    // Access the queryClient to invalidate queries manually
    final queryClient = useQueryClient();
    final scaffoldMessenger = ScaffoldMessenger.of(context);

    // useQuery: Automatically fetches and caches data
    final productsQuery = useQuery<List<String>>(
      key: 'products',
      fetcher: fetchProducts,
    );

    // useMutation: Execute POST/PUT/DELETE networks requests securely
    final addMutation = useMutation<void, Exception, String>(
      (product) => addProductApi(product),
      onSuccess: (_, __) {
        // Automatically refetch the cached list when mutation succeeds
        queryClient.invalidateQueries('products');
        scaffoldMessenger.showSnackBar(
          const SnackBar(content: Text('Product securely added!')),
        );
      },
    );

    return Scaffold(
      appBar: AppBar(
        title: const Text('Basic Query & Mutation'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            tooltip: 'Force Refetch',
            onPressed: () => queryClient.invalidateQueries('products'),
          ),
        ],
      ),
      body: Builder(
        builder: (context) {
          // Loading states are automatically handled
          if (productsQuery.isLoading && !productsQuery.hasData) {
            return const Center(child: CircularProgressIndicator());
          }

          // Error states
          if (productsQuery.isError) {
            return Center(
              child: Text(
                'Oops, an error occurred: ${productsQuery.error}',
                style: const TextStyle(color: Colors.red),
              ),
            );
          }

          final products = productsQuery.data ?? [];

          return RefreshIndicator(
            onRefresh: () async => queryClient.invalidateQueries('products'),
            child: ListView.builder(
              padding: const EdgeInsets.only(bottom: 80),
              itemCount: products.length,
              itemBuilder: (context, index) {
                return ListTile(
                  leading: const CircleAvatar(child: Icon(Icons.inventory_2)),
                  title: Text(products[index]),
                  subtitle: const Text('In stock'),
                );
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: addMutation.isLoading
            ? null // Disable while mutating
            : () => addMutation.mutate('New Apple Device'),
        icon: addMutation.isLoading
            ? const SizedBox(
                width: 20,
                height: 20,
                child: CircularProgressIndicator(strokeWidth: 2),
              )
            : const Icon(Icons.add),
        label: const Text('Add Product'),
      ),
    );
  }
}

// -----------------------------------------------------------------------------
// INFINITE QUERY EXAMPLE
// -----------------------------------------------------------------------------

class InfiniteQueryView extends HookWidget {
  const InfiniteQueryView({super.key});

  @override
  Widget build(BuildContext context) {
    // useInfiniteQuery handles pagination automatically
    final infiniteQuery = useInfiniteQuery<List<String>, int>(
      key: 'infinite_items',
      fetcher: fetchPage,
      getNextPageParam: (lastPage, allPages) {
        // In real apps, you'd check if lastPage has elements, or if
        // a 'nextCursor' exists. Here, we limit to 5 pages.
        if (allPages.length >= 5) return null;
        return allPages.length + 1; // fetch next page
      },
    );

    return Scaffold(
      appBar: AppBar(title: const Text('Infinite queries')),
      body: Builder(
        builder: (context) {
          if (infiniteQuery.isLoading && infiniteQuery.data == null) {
            return const Center(child: CircularProgressIndicator());
          }

          if (infiniteQuery.isError) {
            return Center(child: Text('Error: ${infiniteQuery.error}'));
          }

          final pages = infiniteQuery.data ?? [];
          // Flatten standard paginated lists into generic items array
          final allItems = pages.expand((page) => page).toList();

          return ListView.builder(
            itemCount: allItems.length + 1,
            itemBuilder: (context, index) {
              // Reached the end of the flat list
              if (index == allItems.length) {
                if (!infiniteQuery.hasNextPage) {
                  return const Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Center(
                      child: Text('No more items to load',
                          style: TextStyle(color: Colors.grey)),
                    ),
                  );
                }

                return Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Center(
                    child: ElevatedButton(
                      onPressed: infiniteQuery.isFetchingNextPage
                          ? null
                          : infiniteQuery.fetchNextPage,
                      child: infiniteQuery.isFetchingNextPage
                          ? const SizedBox(
                              height: 16,
                              width: 16,
                              child: CircularProgressIndicator(
                                strokeWidth: 2,
                              ),
                            )
                          : const Text('Load More'),
                    ),
                  ),
                );
              }

              return ListTile(
                leading: CircleAvatar(child: Text('${index + 1}')),
                title: Text(allItems[index]),
              );
            },
          );
        },
      ),
    );
  }
}
1
likes
160
points
240
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A powerful, React Query-inspired data fetching and server state management library for Flutter. Supports caching, retries, infinite queries, and mutations.

Repository (GitHub)
View/report issues

Topics

#state-management #react-query #caching #data-fetching #async

License

MIT (license)

Dependencies

equatable, flutter, flutter_hooks

More

Packages that depend on flutter_query_plus