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.

Hexsis Infinity Scroll (Core) #

Zero-dependency infinite scroll package for Flutter with built-in widgets for ListView, GridView, and Sliver support.

Features #

  • Zero dependencies (only Flutter SDK)
  • 🎨 4 built-in widgets: ListView, GridView, SliverList, SliverGrid
  • 🎯 3 fetcher APIs: Simple, Tuple, or Full control
  • 📱 Pull-to-refresh built-in
  • 🔄 Auto-loading with configurable threshold
  • 🎨 Default UI components: Loading, error, empty states
  • Debounced scroll detection
  • 🛡️ Error handling with retry support

Installation #

dependencies:
  hexsis_infinity_scroll: ^0.2.3

Quick Start #

Ultra-Simple (Auto-detect pagination) #

InfiniteScrollListView<Product>(
  simpleFetcher: (page) async {
    return await api.getProducts(page); // Returns List<Product>
  },
  itemBuilder: (context, product, index) {
    return ProductCard(product);
  },
)

With Tuple (Control hasMore) #

InfiniteScrollListView<Product>(
  tupleFetcher: (page) async {
    final products = await api.getProducts(page);
    return (items: products, hasMore: products.length >= 20);
  },
  itemBuilder: (context, product, index) {
    return ProductCard(product);
  },
)

Full Control #

InfiniteScrollListView<Product>(
  fullFetcher: (page) async {
    final response = await api.getProducts(page);
    return PaginatedData(
      items: response.products,
      currentPage: response.page,
      totalPages: response.totalPages,
    );
  },
  itemBuilder: (context, product, index) {
    return ProductCard(product);
  },
)

Widgets #

InfiniteScrollListView #

InfiniteScrollListView<Product>(
  simpleFetcher: fetchProducts,
  itemBuilder: (context, item, index) => ProductCard(item),
  
  // Optional customization
  separatorBuilder: (context, index) => Divider(),
  loadingBuilder: (context) => CustomLoadingWidget(),
  errorBuilder: (context, error, retry) => CustomErrorWidget(error, retry),
  emptyBuilder: (context) => CustomEmptyWidget(),
  loadingMoreBuilder: (context) => CustomLoadingMoreWidget(),
  
  config: InfiniteScrollConfig(
    threshold: 0.8,
    initialPage: 1,
    pageSize: 20,
    autoLoadOnInit: true,
    debounceMilliseconds: 300,
  ),
)

InfiniteScrollGridView #

InfiniteScrollGridView<Product>(
  simpleFetcher: fetchProducts,
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    crossAxisSpacing: 8,
    mainAxisSpacing: 8,
  ),
  itemBuilder: (context, item, index) => ProductCard(item),
)

InfiniteScrollSliverList & InfiniteScrollSliverGrid #

Note: Sliver widgets require an external ScrollController from the parent CustomScrollView.

class MyScreen extends StatefulWidget {
  @override
  State createState() => _MyScreenState();
}

class _MyScreenState extends State<MyScreen> {
  late ScrollController _scrollController;

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

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

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      controller: _scrollController,  // Pass to CustomScrollView
      slivers: [
        SliverAppBar(title: Text('Products')),
        InfiniteScrollSliverList<Product>(
          scrollController: _scrollController,  // Required!
          simpleFetcher: fetchProducts,
          itemBuilder: (context, item, index) => ProductCard(item),
        ),
      ],
    );
  }
}

For grid layout:

InfiniteScrollSliverGrid<Product>(
  scrollController: _scrollController,  // Same controller!
  simpleFetcher: fetchProducts,
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, item, index) => ProductCard(item),
)

Why ScrollController? Sliver widgets in CustomScrollView can't use NotificationListener internally. Passing the parent ScrollController enables reliable scroll detection with minimal boilerplate.

Controller API (Advanced) #

For manual control, use the controller directly:

class ProductListScreen extends StatefulWidget {
  @override
  State<ProductListScreen> createState() => _ProductListScreenState();
}

class _ProductListScreenState extends State<ProductListScreen> {
  late InfiniteScrollController<Product> _controller;

  @override
  void initState() {
    super.initState();
    _controller = InfiniteScrollController<Product>(
      simpleFetcher: fetchProducts,
      config: InfiniteScrollConfig(threshold: 0.8),
    );
    _controller.addListener(() => setState(() {}));
  }

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

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: _controller.scrollController,
      itemCount: _controller.items.length,
      itemBuilder: (context, index) {
        return ProductCard(_controller.items[index]);
      },
    );
  }
}

Configuration #

InfiniteScrollConfig(
  threshold: 0.8,              // Percentage for small lists
  thresholdPixels: 500.0,      // Fixed pixels from bottom for large lists
  thresholdBreakpoint: 5000.0, // Switch point between strategies
  useSmartThreshold: true,     // Enable smart threshold logic
  initialPage: 1,              // Starting page
  pageSize: 20,                // Items per page (for auto-detection)
  enablePullToRefresh: true,   // Enable pull-to-refresh
  autoLoadOnInit: true,        // Auto-load first page
  debounceMilliseconds: 300,   // Debounce scroll events
)

Smart Threshold Logic #

By default, the package uses smart threshold logic to prevent premature loading in long lists:

  • Small lists (<5000px): Uses percentage (default: 80%)
  • Large lists (≥5000px): Uses fixed pixels from bottom (default: 500px)

This ensures consistent loading behavior regardless of list size.

How It Works #

List Size Old Behavior Smart Threshold Distance from Bottom
1000px 800px (80%) 800px (80%) 200px ✅
5000px 4000px (80%) 4500px (fixed) 500px ✅
10000px 8000px (80%) 9500px (fixed) 500px ✅
20000px 16000px (80%) 19500px (fixed) 500px ✅

Why this matters: With the old approach, a 20,000px list would start loading when you're still 4,000px away - wasting API calls and loading data the user will never see. Smart threshold keeps it consistent at 500px.

Customize Smart Threshold #

InfiniteScrollConfig(
  thresholdPixels: 300.0,       // Closer to bottom
  thresholdBreakpoint: 3000.0,  // Earlier switch point
)

Disable Smart Threshold #

InfiniteScrollConfig(
  useSmartThreshold: false,  // Use classic percentage-only behavior
)

Extension Packages #

For state management integration:

  • hexsis_infinity_scroll_bloc - BLoC/Cubit support
  • hexsis_infinity_scroll_riverpod - Riverpod support
  • hexsis_infinity_scroll_hooks - Hooks wrappers

Reliability #

Pending fetches are safely ignored when their controller or widget is disposed. The package is covered by behavioral tests for pagination strategies, end-of-data detection, retries, errors, duplicate request prevention, refresh, thresholds, and list, grid, and sliver rendering.

License #

MIT License - Copyright (c) 2025 Hexsis Enterprise LLC

0
likes
160
points
567
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