Animated Infinite Scroll view

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

pub.dev package

Overview:

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/passenger.dart';
import 'package:animated_infinite_scroll_pagination/animated_infinite_scroll_pagination.dart';
import 'package:example/models/passengers_response.dart';
import 'package:http/http.dart' as http;


class PassengerRepository {
  final _controller = StreamController<PaginationState<List<Passenger>>>();

  Stream<PaginationState<List<Passenger>>> get result async* {
    yield* _controller.stream;
  }

  Future<int> getPassengersList(int page) async {
    /// emit loading
    _controller.add(const PaginationLoading());

    /// fetch data from server
    final api = "${Env.paginationApi}?page=$page&size=${Env.perPage}";
    try {
      final http.Response response = await http.get(Uri.parse(api));
      final responseData = PassengersListResponse.fromJson(jsonDecode(response.body));
      final passengers = responseData.data ?? [];

      /// emit fetched data
      _controller.add(PaginationSuccess(passengers));
      return responseData.totalPassengers ?? 0;
    } catch (_) {
      /// emit error
      _controller.add(const PaginationError());
      return 0;
    }
  }
}

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

class PassengersViewModel extends PaginationViewModel<Passenger> {
	final repository = PassengerRepository();
	
	/// Sorts this list according to the order specified by the [compare] function.
	@override
	Function(Passenger  a, Passenger  b) compare = ((a, b) => a.id.toString().compareTo(b.id.toString()));

	/// sort inserted items
	@override
	bool  sortItems = false;

	/// decide whether two object represent the same Item
	@override
	bool  areItemsTheSame(Passenger  a, Passenger  b) {
		return  a.id == b.id;
	}

	/// fetch data from repository and emit by Stream to pagination-list
	///
	/// set total items count -> stop loading on last page
	@override
	Future<void> fetchData(int  page) async {
		final  total = await  repository.getPassengersList(page);
		// tell the view-model the total of items.
		// this will stop loading more data when last data-chunk is loaded
		setTotal(total); 
	}

	/// subscribe for list changes
	@override
	Stream<PaginationState<List<Passenger>>> streamSubscription() => repository.result;

	/// remove an item from passengers list
	void  remove(Passenger  passenger) {
		// `params` is a variable declared in `PaginationViewModel`
		// which contains the List<T>
		final  index = params.itemsList.value.items.indexWhere((element) => element.item.id == passenger.id);
		// `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 = PassengersViewModel();

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

@override
void dispose() {
    viewModel.dispose();
    super.dispose();
}
  • Wrap the animated scrollView in your screen:

  deletePassenger(Passenger passenger) {
  	viewModel.remove(passenger);
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedInfiniteScrollView<Passenger>(
      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: (item) => PassengerCard(passenger: item, onDelete: deletePassenger),
      refreshIndicator: true,
    );
  }

AnimatedInfiniteScrollView Params:

  • viewModel: The View-Model you declared above in this example (required).
  • 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).

GitHub

View Github