CI codecov style license

rx_bloc_list

The rx_bloc_list package facilitates implementing infinity scroll and pull-to-refresh features with minimal setup. This package is meant to be used along with RxBloc ecosystem.

Table of contents

Usage

Before using the actual package add it to the pubspec.yaml dependencies:

dependencies:
  rx_bloc_list: any

Also be sure to import the package:

import 'package:rx_bloc_list/rx_bloc_list.dart';

Now you can include the RxPaginatedBuilder in your project like this:

class PaginatedListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Scaffold(
        body: SafeArea(
          child: RxPaginatedBuilder<UserBlocType, User>.withRefreshIndicator(
            state: (bloc) => bloc.states.paginatedList,
            onBottomScrolled: (bloc) => bloc.events.loadPage(),
            onRefresh: (bloc) async {
              bloc.events.loadPage(reset: true);
              return bloc.states.refreshDone;
            },
            buildSuccess: (context, list, bloc) => ListView.builder(
              itemBuilder: (context, index) {
                final user = list.getItem(index);

                if (user == null) {
                  return const YourProgressIndicator();
                }

                return YourListTile(user: user);
              },
              itemCount: list.itemCount,
            ),
            buildLoading: (context, list, bloc) =>
                const YourProgressIndicator(),
            buildError: (context, list, bloc) =>
                YourErrorWidget(error: list.error!),
          ),
        ),
      );
}
Initial loading of the data Default app state
Initial loading Default state
Loading of the next page (infinity scroll) Data refreshing (pull to refresh)
Infinity loading Refreshing

Setup

In order to make use of the RxPaginatedBuilder, you first need to specify the following required parameters:

  • state is the state of the BLoC that will be listened for changes. The state is a Stream<PaginatedList<T>> where T is the type of the data that is being paginated.
  • buildSuccess is a callback which is invoked when the list is not empty or when the next page is being loaded.
  • buildError is a callback which is invoked when the state is of type Result.error
  • buildLoading is a callback which is invoked only on the initial loading.
  • onBottomScrolled is a callback that is executed once the end of the list is reached. This can be, for instance, used for fetching the next page of data.

Additional parameters

RxPaginatedBuilder also comes with additional optional parameters that can be adjusted to your needs.

The wrapperBuilder method is a builder method with the intention of creating a wrapper widget around the child widget built using the buildSuccess, buildError and buildLoading methods. The wrapperBuilder method gives you access to the BuildContext, BLoC containing the state that is listened and the Widget that is build with the builders method. This method can be used for adding additional functionality or help in cases when the built child widget is needed beforehand.

You can manage the execution of the onBottomScrolled parameter by enabling or disabling it via the enableOnBottomScrolledCallback.

Additionally, you can define the minimum scroll threshold which will execute the onBottomScrolled callback by changing the value of scrollThreshold. The default value of the scroll threshold is 100 pixels.

The RxPaginatedBuilder also provides the ability to react to scrolling via the onScrolled callback, with a parameter telling whether the user is or has stopped scrolling.

There may be cases where you have a reference to the BLoC that is used by the RxPaginatedBuilder. By specifying the bloc parameter you remove the need to perform a lookup for that BLoC in the widget tree, improving the performance by a small bit.

RxPaginatedBuilder.withRefreshIndicator

Sometimes, you may want to have a working pagination and pull-to-refresh without spending too much time on it. Using the RxPaginatedBuilder.withRefreshIndicator gives you access to a Refresh Indicator straight out of the box.

Along with the required parameters of the default implementation, RxPaginatedBuilder.withRefreshIndicator gets rid of the wrapperBuilder but introduces a new required parameter onRefresh. The onRefresh callback is triggered once a pull-to-refresh has been performed. The callback, containing the BLoC as a parameter, should return a future, which once complete will make the refresh indicator disappear.

withLatestFromIdentifiableList()

If you want to keep the list of items inside your bloc up to date based on a stream of updated items, you can use the withLatestFromIdentifiableList() extension method. It merges, removes or ignores the value of the stream from the latest list value. The result, based on the operationCallback, will be emitted as a new value(ManagedList). With the help of the CoordinatorBloc our two features can communicate with each other and have different or the same elements from the same list collection. In the bellow example, for every operation we have, a state in the CoordinatorBloc is updated and an operationCallback is executed.

states.onReminderCreated.whereSuccess()
    .withLatestFromIdentifiableList(
      reminderList,
      operationCallback: (ReminderModel createdModel, List<ReminderModel> list) async {
        if (list.isNotEmpty) {
          final lastElementDueDate = list.last.dueDate;
          if (lastElementDueDate.compareTo(createdModel.dueDate) > 0) {
            return ManageOperation.merge;
          }
        }
        return ManageOperation.ignore;
      },
    )
    .bind(reminderList)

The method withLatestFromIdentifiableList() accepts two parameters. The list parameter should be of type Stream<List<E>>, where E extends Identifiable. The model implementing Identifiable, should provide implementation for the bool isEqualToIdentifiable(Identifiable other) method, so that the identifiable extension method knows how to distinguish between the models in the list. The operationCallback parameter should be a function, which receives as first parameter the Identifiable model and as second the list. The returned value from the function is a ManageOperation.

For more complex/production ready example please look at the Reminders example app.

Video tutorials

  • Building feature-rich lists in Flutter A feature-rich ListView implementation in Flutter that demonstrates how easy it is to build common functionalities such as pull-to-refresh and infinite-scroll.

Articles