Flutter Dart

Scroll Infinity

A Flutter widget that provides an infinite scrollable list with built-in support for paginated data loading, state handling, and flexible customization.
Explore the docs »

View on pub.dev · Report Bug · Request Feature

Table of Contents

About The Project

ScrollInfinity simplifies implementing infinite scroll lists in Flutter. It handles paginated loading, state management (loading, empty, error), and user interactions, letting developers focus on building item UIs.

It offers customization for manual/automatic loading, custom state widgets, and both vertical and horizontal layouts.

Development Environment

Tool Version Used
Flutter SDK 3.32.7
Dart SDK 3.8.1

Features

  • Infinite scroll with pagination
  • Manual or automatic data loading
  • Custom "Load More" and "Try Again" builders
  • Loading, error, and empty states handling
  • Optional scrollbars, header widget, and separators
  • Vertical and horizontal scrolling support
  • Initial items support
  • Insert null values at intervals (ads/dividers)
  • Retry attempts limit on error
  • Real item index mapping with intervals

Built With

  • Flutter – UI toolkit by Google for mobile, web, and desktop
  • Dart – Language optimized for fast cross-platform apps

Getting Started

Install:

flutter pub add scroll_infinity

Usage

Pagination requests a new page when the user reaches the list end. If loadData returns null, the error state is shown.

Note: Use a nullable type T? when using interval, as null values are inserted.

Basic Vertical Scroll


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

void main() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyApp(),
    ),
  );
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static const _maxItems = 20;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ScrollInfinity<int>(
          maxItems: _maxItems,
          loadData: (page) async {
            await Future.delayed(const Duration(seconds: 2));

            return List.generate(
              _maxItems,
              (index) => page * _maxItems + index + 1,
            );
          },
          itemBuilder: (value, index) {
            return ListTile(
              title: Text('Item $value'),
              subtitle: Text('Subtitle $value'),
            );
          },
        ),
      ),
    );
  }
}

Basic Horizontal Scroll


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

void main() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyApp(),
    ),
  );
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static const _maxItems = 10;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          height: 100.0,
          child: ScrollInfinity<int>(
            scrollDirection: Axis.horizontal,
            maxItems: _maxItems,
            loadData: (page) async {
              await Future.delayed(const Duration(seconds: 2));

              return List.generate(
                _maxItems,
                (index) => page * _maxItems + index + 1,
              );
            },
            itemBuilder: (value, index) {
              return Center(
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text('Item $value'),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

Vertical Scroll with Interval


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

void main() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyApp(),
    ),
  );
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static const _maxItems = 20;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: ScrollInfinity<int?>(
          maxItems: _maxItems,
          interval: 2,
          loadData: (page) async {
            await Future.delayed(const Duration(seconds: 2));

            return List.generate(
              _maxItems,
              (index) => page * _maxItems + index + 1,
            );
          },
          itemBuilder: (value, index) {
            if (value == null) return const Divider();

            return ListTile(
              title: Text('Item $value'),
            );
          },
        ),
      ),
    );
  }
}

Properties

Core Data Handling

Name Type Default Description
loadData Future<List<T>?> Function(int) Fetch data for each page
itemBuilder Widget Function(T value, int index) Builds each item
maxItems int Max items per request
initialItems List<T>? null Items before first fetch
initialPageIndex int 0 Starting page index

Layout & Appearance

Name Type Default Description
scrollDirection Axis vertical Scroll direction
padding EdgeInsetsGeometry? null Internal padding
header Widget? null Header widget
separatorBuilder Widget Function(BuildContext, int)? null Separators
scrollbars bool true Show scrollbars

Behavioral Features

Name Type Default Description
interval int? null Null item insertion interval
useRealItemIndex bool true Independent indexing
automaticLoading bool true Auto-fetch on scroll

Error Handling

Name Type Default Description
enableRetryOnError bool true Allow retry
maxRetries int? null Retry limit

State-Specific Widgets

Name Type Default Description
loading Widget? null Loading state widget
empty Widget? null Empty state widget
tryAgainBuilder Widget Function(VoidCallback)? null "Try Again" widget
loadMoreBuilder Widget Function(VoidCallback)? null "Load More" widget
retryLimitReached Widget? null Retry limit reached widget

Contributing

  1. Fork the project

  2. Create a feature branch:

    git checkout -b feature/AmazingFeature
    
  3. Commit changes:

    git commit -m 'Add some AmazingFeature'
    
  4. Push to branch:

    git push origin feature/AmazingFeature
    
  5. Open a Pull Request

License

Distributed under the MIT License. See the LICENSE file for more information.

Author

Developed by Dário Matias:

Libraries

scroll_infinity