Animated Infinite Scroll view

load and display small chunks of items as the user scrolls down the screen


1- Declare View-Model extends PaginationViewModel<T>

  • PaginationViewModel the layer between user interface and model which is handled by a repository.
  • T is Type of your data list. for Example if you have List<User> , you will create a viewModel extends PaginationViewModel<User>.

Before create the View-Model we're going to create our Repository. The Repository is the layer between the View-Model and back-end.

The Repository will be something like this:

import 'dart:async';
import 'dart:convert';
import 'package:example/config/env.dart';
import 'package:example/models/users_list.dart';
import 'package:example/models/users_response.dart';
import 'package:http/http.dart' as http;

class UserRepository {

  Future<UsersList?> getUsersList(int page) async {
    /// fetch data from server
    final api = "${Env.paginationApi}/users?page=$page&limit=${Env.perPage}";
    final http.Response response = await http.get(Uri.parse(api));
    final responseData = UserResponse.fromJson(jsonDecode(response.body));
    /// responseData.usersList -> json: { "users": [], "total": 100 }
    return responseData.usersList;

Now we're going to create our View-Model. The View-Model will be something like this:

class UsersViewModel extends PaginationViewModel<User> {
  final repository = UserRepository();
  final _controller = StreamController<PaginationState<List<User>>>();

  Stream<PaginationState<List<User>>> get result async* {

  /// decide whether two object represent the same Item
  bool areItemsTheSame(User a, User b) {
    return ==;

  /// fetch data from repository and emit by Stream to pagination-list
  /// set total items count -> stop loading on last page
  Future<void> fetchData(int page) async {
    // emit loading
    _controller.add(const PaginationLoading());
    try {
      final data = await repository.getUsersList(page);
      // tell the view-model the total of items.
      // this will stop loading more data when last data-chunk is loaded
      if (data?.total != null && data?.users != null) {
        // emit data
    } catch (_) {
      // emit error
      _controller.add(const PaginationError());

  /// subscribe for list changes
  Stream<PaginationState<List<User>>> streamSubscription() => result;

  /// remove an item from users list
  void remove(User user) {
    // `paginationParams` is a variable declared in `PaginationViewModel`
    // which contains the List<T>
    final index = paginationParams.itemsList.value.items.indexWhere((element) => ==;
    // `deleteItem` is a method declared in `PaginationViewModel`
    // which expected a integer value `index of item`
    if (index != -1) deleteItem(index);

2- UI:

  • Declare your view-model in your screen:
final viewModel = UsersViewModel();

void initState() {
      ..listen() // observe data-list changes when repository update the list
      ..getPaginationList(); // fetch first chunk of data from server

void dispose() {
  • Wrap the animated scrollView in your screen:
  deleteUser(User user) {

Widget build(BuildContext context) {
  return AnimatedInfiniteScrollView<User>(
      viewModel: viewModel,
      loadingWidget: const AppProgressBar(), // customize your loading widget
      footerLoadingWidget: const AppProgressBar(), // customize your pagination loading widget
      errorWidget: const Text("Pagination Error"), // customize your error widget
      itemBuilder: (index, item) => UserCard(user: item, onDelete: deleteUser),
      refreshIndicator: true,
      onRefresh: () {
        // handle swipe refresh event

AnimatedInfiniteScrollView Parameters:

  • viewModel: The View-Model you declared above in this example (required).
  • topWidget: a widget you want to place at the top of the first itemBuilder widget (optional).
  • loadingWidget: a widget you want to display when first page is loading (optional).
  • footerLoadingWidget: a widget you want to display when pagination data is loading (optional).
  • errorWidget: a widget you want to display when pagination data is field loading (throw exception) (optional).
  • refreshIndicator: wrap the scroll view inside a RefreshIndicator (optional), default value is false.
  • itemBuilder: a widget function which build your Data Widget inside the scroll view on Each Data Item from list (required).
  • onRefresh: callback called when user swipe refresh to load new list (optional)
  • scrollDirection: Axis.vertical or Axis.horizontal (optional)
  • gridDelegate: A delegate that controls the layout of the children within the GridView (optional)
  • noItemsWidget: a widget appears after fetch first page data and the result is empty (optional)