pagination_grid_view 1.0.1 copy "pagination_grid_view: ^1.0.1" to clipboard
pagination_grid_view: ^1.0.1 copied to clipboard

A Flutter grid view with built-in pagination, pull-to-refresh, empty states, and loading indicators. Perfect for image galleries, product grids, and any paginated grid layout.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:pagination_grid_view/pagination_grid_view.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pagination Grid View Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const ExamplePage(),
    );
  }
}

class ExamplePage extends StatefulWidget {
  const ExamplePage({super.key});

  @override
  State<ExamplePage> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> {
  List<ProductItem> products = [];
  bool isLoading = false;
  bool hasError = false;
  int currentPage = 1;
  final int itemsPerPage = 20;
  final int totalItems = 100;

  @override
  void initState() {
    super.initState();
    _loadInitialData();
  }

  Future<void> _loadInitialData() async {
    setState(() => isLoading = true);
    await Future.delayed(const Duration(seconds: 1));
    setState(() {
      products = _generateProducts(0, itemsPerPage);
      isLoading = false;
      currentPage = 2;
    });
  }

  Future<void> _loadMore() async {
    if (products.length >= totalItems) return;
    setState(() => isLoading = true);
    await Future.delayed(const Duration(milliseconds: 800));

    if (mounted) {
      setState(() {
        final newProducts = _generateProducts(
          products.length,
          itemsPerPage,
        );
        products.addAll(newProducts);
        currentPage++;
        isLoading = false;
      });
    }
  }

  Future<void> _refresh() async {
    await Future.delayed(const Duration(seconds: 1));
    setState(() {
      products = _generateProducts(0, itemsPerPage);
      currentPage = 2;
      hasError = false;
    });
  }

  List<ProductItem> _generateProducts(int start, int count) {
    return List.generate(
      count,
      (i) => ProductItem(
        id: start + i + 1,
        name: 'Product ${start + i + 1}',
        price: (20 + (start + i) % 80).toDouble(),
        color: Colors.primaries[(start + i) % Colors.primaries.length],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Pagination Grid View'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _refresh,
            tooltip: 'Refresh',
          ),
        ],
      ),
      body: PaginationGridView<ProductItem>(
        items: products,
        onLoadMore: _loadMore,
        onRefresh: _refresh,
        itemBuilder: (context, product, index) => ProductCard(
          product: product,
          onTap: () => _showProductDetails(product),
        ),
        crossAxisCount: 2,
        childAspectRatio: 0.75,
        crossAxisSpacing: 12,
        mainAxisSpacing: 12,
        hasMoreData: products.length < totalItems,
        isLoading: isLoading,
        hasError: hasError,
        padding: const EdgeInsets.all(16),
        loadMoreThreshold: 0.8,
        emptyWidget: const EmptyStateWidget(),
        loadingWidget: const LoadingWidget(),
      ),
    );
  }

  void _showProductDetails(ProductItem product) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(product.name),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Container(
              height: 150,
              decoration: BoxDecoration(
                color: product.color.withOpacity(0.3),
                borderRadius: BorderRadius.circular(12),
              ),
              child: Center(
                child: Icon(
                  Icons.shopping_bag,
                  size: 64,
                  color: product.color,
                ),
              ),
            ),
            const SizedBox(height: 16),
            Text('Price: \$${product.price.toStringAsFixed(2)}'),
            Text('Product ID: ${product.id}'),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Close'),
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.pop(context);
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Added ${product.name} to cart')),
              );
            },
            child: const Text('Add to Cart'),
          ),
        ],
      ),
    );
  }
}

// Product Model
class ProductItem {
  final int id;
  final String name;
  final double price;
  final Color color;

  ProductItem({
    required this.id,
    required this.name,
    required this.price,
    required this.color,
  });
}

// Product Card Widget
class ProductCard extends StatelessWidget {
  final ProductItem product;
  final VoidCallback onTap;

  const ProductCard({
    super.key,
    required this.product,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 2,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
      ),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Image placeholder
            Expanded(
              child: Container(
                decoration: BoxDecoration(
                  color: product.color.withOpacity(0.3),
                  borderRadius: const BorderRadius.vertical(
                    top: Radius.circular(12),
                  ),
                ),
                child: Center(
                  child: Icon(
                    Icons.shopping_bag,
                    size: 48,
                    color: product.color,
                  ),
                ),
              ),
            ),
            // Product info
            Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    product.name,
                    style: const TextStyle(
                      fontWeight: FontWeight.bold,
                      fontSize: 14,
                    ),
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '\$${product.price.toStringAsFixed(2)}',
                    style: TextStyle(
                      color: Theme.of(context).colorScheme.primary,
                      fontWeight: FontWeight.w600,
                      fontSize: 16,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// Empty State Widget
class EmptyStateWidget extends StatelessWidget {
  const EmptyStateWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(
          Icons.shopping_cart_outlined,
          size: 80,
          color: Colors.grey.shade400,
        ),
        const SizedBox(height: 16),
        Text(
          'No products found',
          style: TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.w600,
            color: Colors.grey.shade600,
          ),
        ),
        const SizedBox(height: 8),
        Text(
          'Pull down to refresh',
          style: TextStyle(
            fontSize: 14,
            color: Colors.grey.shade500,
          ),
        ),
      ],
    );
  }
}

// Loading Widget
class LoadingWidget extends StatelessWidget {
  const LoadingWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const CircularProgressIndicator(),
        const SizedBox(height: 16),
        Text(
          'Loading products...',
          style: TextStyle(
            color: Colors.grey.shade600,
            fontSize: 14,
          ),
        ),
      ],
    );
  }
}
1
likes
150
points
14
downloads

Documentation

API reference

Publisher

verified publisherdhananjayafernando.online

Weekly Downloads

A Flutter grid view with built-in pagination, pull-to-refresh, empty states, and loading indicators. Perfect for image galleries, product grids, and any paginated grid layout.

Topics

#grid #pagination #infinite-scroll #listview #widget

License

MIT (license)

Dependencies

flutter

More

Packages that depend on pagination_grid_view