Scroll Infinity

Installation

Run this command:

flutter pub add scroll_infinity

Usage Example

Here are some examples of how to use the package to create a list with infinite scrolling:

Vertical:

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

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

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  static const _maxItems = 20;

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

          return List.generate(_maxItems, (index) {
            return _maxItems * pageKey + index + 1;
          });
        },
        itemBuilder: (value, index) {
          return ListTile(
            title: Text('Item $value'),
            subtitle: const Text('Subtitle'),
            trailing: const Icon(
              Icons.keyboard_arrow_right_rounded,
            ),
          );
        },
      ),
    );
  }
}

Horizontal:

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

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

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  static const _maxItems = 6;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          height: 64.0,
          child: ScrollInfinity(
            scrollDirection: Axis.horizontal,
            maxItems: _maxItems,
            loadData: (pageKey) async {
              await Future.delayed(
                const Duration(
                  seconds: 2,
                ),
              );
        
              return List.generate(_maxItems, (index) {
                return _maxItems * pageKey + index + 1;
              });
            },
            itemBuilder: (value, index) {
              return Center(
                child: SizedBox(
                  width: MediaQuery.sizeOf(context).width * 0.5,
                  child: ListTile(
                    onTap: () {},
                    title: Text('Item $value'),
                    subtitle: const Text('Subtitle'),
                    trailing: const Icon(
                      Icons.keyboard_arrow_right_rounded,
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

With Interval

Intervals are identified when the value of itemBuilder is null. To allow the value of itemBuilder to be nullable, facilitating the verification of the value, use ScrollInfinity with the expected value type followed by ?.

import 'dart:math';

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

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

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  static const _maxItems = 20;
  final _random = Random();

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

          if (_random.nextInt(4) == 0) {
            return null;
          }

          return List.generate(_maxItems, (index) {
            return _maxItems * pageKey + index + 1;
          });
        },
        itemBuilder: (value, index) {
          if (value == null) {
            return const SizedBox(
              height: 60.0,
              child: Placeholder(),
            );
          }

          return ListTile(
            title: Text('Item $value'),
            subtitle: const Text('Subtitle'),
            trailing: const Icon(
              Icons.keyboard_arrow_right_rounded,
            ),
          );
        },
      ),
    );
  }
}

With Loader

Add custom loading for initial items.

import 'dart:math';

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

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

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  final _notifier = ScrollInfinityInitialItemsNotifier<int?>(null);

  static const _maxItems = 20;
  final _random = Random();

  Future<void> _initLoader() async {
    _notifier.value = await _loadData(0);
  }

  Future<List<int>?> _loadData(int pageIndex) async {
    await Future.delayed(
      const Duration(
        seconds: 2,
      ),
    );

    if (_random.nextInt(4) == 0) {
      return null;
    }

    return List.generate(_maxItems, (index) {
      return _maxItems * pageIndex + index + 1;
    });
  }

  @override
  void initState() {
    _initLoader();

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ScrollInfinityLoader(
        notifier: _notifier,
        scrollInfinityBuilder: (items) {
          return ScrollInfinity<int?>(
            maxItems: _maxItems,
            initialPageIndex: 1,
            initialItems: items,
            interval: 2,
            loadData: _loadData,
            itemBuilder: (value, index) {
              if (value == null) {
                return const SizedBox(
                  height: 60.0,
                  child: Placeholder(),
                );
              }

              return ListTile(
                title: Text('Item $value'),
                subtitle: const Text('Subtitle'),
                trailing: const Icon(
                  Icons.keyboard_arrow_right_rounded,
                ),
              );
            },
          );
        },
      ),
    );
  }
}

Properties

  • scrollDirection: Defines the scrolling direction of the list. Can be Axis.vertical or Axis.horizontal.
scrollDirection: Axis.vertical,
  • scrollbars: Shows scrollbars if true. Default is false.
scrollbars: true,
  • padding: Specifies the internal padding of the list.
padding: EdgeInsets.all(8.0),
  • header: Listing header.
header: HeaderWidget(),
  • initialPageIndex: Initial page index. Default is 0.
initialPageIndex: 1,
  • enableRetryOnError: Determines if retrying to load data after an error is enabled. Default is true.
enableRetryOnError: false,
  • empty: Widget used to display custom content when the list is empty.
empty: Text('No items available.'),
  • reset: Widget used to display custom content during a reset.
reset: Text('Reseting...'),
  • error: Widget used to display custom content when an error occurs.
error: Text('Error occurred.'),
  • initialItems: Specifies the initial items to be displayed in the list.
initialItems: <Widget>[
  // items
],
  • interval: Specifies the range in which the null value is passed.
interval: 20,
  • loading: Allows passing a custom loading component.
loading: LoadingWidget(),
  • loadingStyle: Defines the style of the CircularProgressIndicator.
loadingStyle: CircularProgressIndicator(
  color: Colors.blue,
  strokeWidth: 8.0,
),
  • tryAgainButtonBuilder: Allows passing a custom retry button component, triggered by a callback.
tryAgainButtonBuilder: (action) {
  return ElevatedButton(
    onPressed: action,
    child: Text('Retry'),
  );
},
  • maxItems: Specifies the maximum number of items per request. This will be used to determine when the list reaches the end.
maxItems: 20,
  • loadData: Function responsible for loading the data. It should return a list of items.
loadData: (pageIndex) async {
  // Logic to load the data
},
  • separatorBuilder: Builds the separator component between the items in the list.
separatorBuilder: (context, index) {
  return Divider(
    color: Colors.grey,
    height: 1.0,
  );
},
  • itemBuilder: Builds the items in the list. This function should return the widget that represents each item in the list.
itemBuilder: (value, index) {
  final item = items[index];

  return ListTile(
    title: Text(item.title),
    subtitle: Text(item.subtitle),
  );
},

Author

This Flutter package was developed by Dário Matias.

Donations

Help maintain the project with donations.

"Buy Me A Coffee"

Libraries

scroll_infinity