hexsis_infinity_scroll 0.2.3 copy "hexsis_infinity_scroll: ^0.2.3" to clipboard
hexsis_infinity_scroll: ^0.2.3 copied to clipboard

Production-ready infinite scroll package for Flutter with zero dependencies. Supports ListView, GridView, and Sliver widgets with built-in loading, error, and empty states.

example/lib/main.dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hexsis_infinity_scroll/hexsis_infinity_scroll.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Hexsis Infinity Scroll Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Infinity Scroll Examples'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _ExampleCard(
            title: 'Simple ListView',
            description: 'Auto-detect pagination',
            onTap:
                () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => const SimpleListExample(),
                  ),
                ),
          ),
          _ExampleCard(
            title: 'GridView',
            description: 'Grid layout with infinite scroll',
            onTap:
                () => Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => const GridExample()),
                ),
          ),
          _ExampleCard(
            title: 'Sliver Example',
            description: 'CustomScrollView with sliver widgets',
            onTap:
                () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => const SliverExample(),
                  ),
                ),
          ),
          _ExampleCard(
            title: 'Controller API',
            description: 'Manual control with controller',
            onTap:
                () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => const ControllerExample(),
                  ),
                ),
          ),
        ],
      ),
    );
  }
}

class _ExampleCard extends StatelessWidget {
  const _ExampleCard({
    required this.title,
    required this.description,
    required this.onTap,
  });

  final String title;
  final String description;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      child: ListTile(
        title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
        subtitle: Text(description),
        trailing: const Icon(Icons.arrow_forward_ios, size: 16),
        onTap: onTap,
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Simple ListView')),
      body: InfiniteScrollListView<Product>(
        simpleFetcher: (page) async => ProductApi.fetchProducts(page),
        itemBuilder: (context, product, index) {
          return ProductListTile(product: product, index: index);
        },
        separatorBuilder: (context, index) => const Divider(height: 1),
        config: const InfiniteScrollConfig(pageSize: 20),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('GridView')),
      body: InfiniteScrollGridView<Product>(
        simpleFetcher: (page) async => ProductApi.fetchProducts(page),
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 12,
          mainAxisSpacing: 12,
          childAspectRatio: 0.75,
        ),
        itemBuilder: (context, product, index) {
          return ProductGridCard(product: product);
        },
        config: const InfiniteScrollConfig(pageSize: 20),
      ),
    );
  }
}

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

  @override
  State<SliverExample> createState() => _SliverExampleState();
}

class _SliverExampleState extends State<SliverExample> {
  late ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _scrollController = ScrollController();
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        controller: _scrollController,
        slivers: [
          SliverAppBar(
            expandedHeight: 200,
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              title: const Text('Sliver Example'),
              background: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [
                      Theme.of(context).colorScheme.primary,
                      Theme.of(context).colorScheme.secondary,
                    ],
                  ),
                ),
              ),
            ),
          ),
          InfiniteScrollSliverList<Product>(
            scrollController: _scrollController,
            simpleFetcher: (page) async => ProductApi.fetchProducts(page),
            itemBuilder: (context, product, index) {
              return ProductListTile(product: product, index: index);
            },
            config: const InfiniteScrollConfig(pageSize: 20),
          ),
        ],
      ),
    );
  }
}

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

  @override
  State<ControllerExample> createState() => _ControllerExampleState();
}

class _ControllerExampleState extends State<ControllerExample> {
  late InfiniteScrollController<Product> _controller;

  @override
  void initState() {
    super.initState();
    _controller = InfiniteScrollController<Product>(
      simpleFetcher: (page) async => ProductApi.fetchProducts(page),
      config: const InfiniteScrollConfig(pageSize: 20),
    );
    _controller.addListener(() => setState(() {}));
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Controller API'),
        actions: [
          IconButton(
            icon: const Icon(Icons.info_outline),
            onPressed: () {
              showDialog(
                context: context,
                builder:
                    (context) => AlertDialog(
                      title: const Text('Controller Info'),
                      content: Text(
                        'Items: ${_controller.items.length}\n'
                        'Page: ${_controller.currentPage}\n'
                        'Has More: ${_controller.hasMore}\n'
                        'Loading: ${_controller.isLoading}\n'
                        'Loading More: ${_controller.isLoadingMore}',
                      ),
                      actions: [
                        TextButton(
                          onPressed: () => Navigator.pop(context),
                          child: const Text('Close'),
                        ),
                      ],
                    ),
              );
            },
          ),
        ],
      ),
      body: ListView.builder(
        controller: _controller.scrollController,
        itemCount:
            _controller.items.length + (_controller.isLoadingMore ? 1 : 0),
        itemBuilder: (context, index) {
          if (index == _controller.items.length) {
            return const Center(
              child: Padding(
                padding: EdgeInsets.all(16.0),
                child: CircularProgressIndicator(),
              ),
            );
          }
          return ProductListTile(
            product: _controller.items[index],
            index: index,
          );
        },
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: _controller.canLoadMore ? _controller.loadMore : null,
        icon: const Icon(Icons.download),
        label: const Text('Load More'),
      ),
    );
  }
}

class Product {
  final int id;
  final String name;
  final String description;
  final double price;
  final String imageUrl;

  Product({
    required this.id,
    required this.name,
    required this.description,
    required this.price,
    required this.imageUrl,
  });
}

class ProductApi {
  static Future<List<Product>> fetchProducts(int page) async {
    await Future.delayed(const Duration(milliseconds: 800));

    if (page > 10) {
      return [];
    }

    return List.generate(20, (index) {
      final id = (page - 1) * 20 + index + 1;
      return Product(
        id: id,
        name: 'Product #$id',
        description: 'Description for product $id',
        price: Random().nextDouble() * 100 + 10,
        imageUrl: 'https://picsum.photos/200?random=$id',
      );
    });
  }
}

class ProductListTile extends StatelessWidget {
  const ProductListTile({
    required this.product,
    required this.index,
    super.key,
  });

  final Product product;
  final int index;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: CircleAvatar(child: Text('#${index + 1}')),
      title: Text(product.name),
      subtitle: Text(product.description),
      trailing: Text(
        '\$${product.price.toStringAsFixed(2)}',
        style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
      ),
    );
  }
}

class ProductGridCard extends StatelessWidget {
  const ProductGridCard({required this.product, super.key});

  final Product product;

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Expanded(
            child: Container(
              color: Colors.primaries[product.id % Colors.primaries.length],
              child: Center(
                child: Text(
                  '#${product.id}',
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 32,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  product.name,
                  style: const TextStyle(fontWeight: FontWeight.bold),
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 4),
                Text(
                  '\$${product.price.toStringAsFixed(2)}',
                  style: TextStyle(
                    color: Theme.of(context).colorScheme.primary,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
160
points
580
downloads

Documentation

API reference

Publisher

verified publisherhexsis.com

Weekly Downloads

Production-ready infinite scroll package for Flutter with zero dependencies. Supports ListView, GridView, and Sliver widgets with built-in loading, error, and empty states.

Homepage

License

MIT (license)

Dependencies

flutter

More

Packages that depend on hexsis_infinity_scroll