Infinite Paging ScrollView
Encapsulate animated_infinite_scroll_pagination package with common (loading, footer, error) widgets and functionality.Attributes:
Name  | Type  | Description  | Default Value  | Required  | Nullable  | 
|---|---|---|---|---|---|
| controller |  | class extends [AnimatedInfinitePaginationController] | - | ||
| itemBuilder |  | callback return [Widget] with item [T], to display item card in listView | - | ||
| topWidgets |  | pass [topWidgets] when you want to place a widget at the top of the first [itemBuilder] widget. | - | ||
| onRefresh |  | callback called when user swipe to refresh list. | - | ||
| retry |  | callback called when pagination request failed and user press on [errorWidget]. | - | ||
| refreshIndicator |  | wrap [ScrollView] in [RefreshIndicator]. |  | ||
| scrollDirection |  | The [Axis] along which the scroll view's offset increases. |  | ||
| gridDelegate |  | A delegate that controls the layout of the children within the [GridView]. | - | ||
| spawnIsolate |  | Whether to spawn a new isolate on which to calculate the diff on. | - | ||
| noItemsWidget |  | widget appears after fetch first page data and the result is empty. | - | ||
| loadingWidget |  | widget appears when first page is in loading state. | - | 
Implementing AnimatedInfinitePaginationController
Paging Controller works on fetch parts of data and hold this data, handle events like (refreshIndicator) event,We recommend implementing PagingController with Scoped SingletonHere is example to fetch ListResponse of Products with paging:import 'package:injectable/injectable.dart';
import 'package:animated_infinite_scroll_pagination/animated_infinite_scroll_pagination.dart';
@LazySingleton(scope: 'productsPaging')
class ProductsPagingController with AnimatedInfinitePaginationController<Product> {
  final ProductsRepository _repository;
  ProductsPagingController(this._repository);
  /// decide whether two object represent the same Item
  @override
  bool areItemsTheSame(Product oldItem, Product newItem) {
    return oldItem.id == newItem.id;
  }
  /// fetch data from repository and emit new state
  @override
  Future<void> fetchData(int page) async {
    // emit loading
    emitState(const PaginationLoadingState());
    // fetch data from server
    try {
      await for (final response in _repository.findAll(page)) {
        final result = response.data;
        if (result?.data != null && response.isSuccess) {
          // emit fetched data
          //
          // when emit remote data after cached data with same page,
          // controller will replace the cached data with remote data
          emitState(PaginationSuccessState(result!.data!.toList(), cached: result.cached));
          // tell the controller the total of items,
          // this will stop loading more data when last data-chunk is loaded.
          setTotal(result.total!);
        } else {
          // emit error
          emitState(const PaginationErrorState());
        }
      }
    } catch (error) {
      // emit error
      emitState(const PaginationErrorState());
      if (kDebugMode) print(error);
    }
  }
  /// cancel all pending requests
  void dispose() {
    _repository.dispose();
  }
}Implementing AnimatedInfiniteScrollView
class ProductsListScreen extends StatefulWidget {
  const ProductsListScreen({super.key});
  @override
  State<ProductsListScreen> createState() => _ProductsListScreenState();
}
class _ProductsListScreenState extends State<ProductsListScreen> 
  with LifecycleOwner<ProductsListScreen, ProductsListViewModel>, ObserverMixin {
  @override
  DiScope get diScope => DiScope(
    name: 'productsList', // (viewModel) scope
    factory: () => GetIt.instance.initProductsListScope(),
    dependencies: [
      // be sure to make the 'initProductsScope' before 'initProductsPagingScope'
      DiScope(
        name: 'products', // (repository and data-source) scope
        factory: () => getIt.initProductsScope(),
      ),
      DiScope(
        name: 'productsPaging', // (paging controller) scope
        factory: () => getIt.initProductsPagingScope(),
      ),
    ],
  );
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnimatedInfiniteScrollView<Product>(
        controller: GetIt.instance<ProductsPagingController>(), // injected pagingController
        options: AnimatedInfinitePaginationOptions(
          loadingWidget: const CircularProgressIndicator.adaptive(), // Displayed when loading first page
          errorWidget: const Center(child: Text('An error occurred')), // Displayed on error
          itemBuilder: (BuildContext context, Product product, int index) {
            // Build the UI for each product item
            return ListTile(
              title: Text(product.name!),
              subtitle: Text(product.description!),
            );
          },
        ),
      ),
    );
  }
}