flutter_bloc_patterns 0.3.1

  • Readme
  • Changelog
  • Example
  • Installing
  • 86

Flutter BLoC patterns #

Codemagic build status Star on GitHub License: MIT


A set of most common BLoC use cases build on top flutter_bloc library.

Key contepts #

BLoC #

BLoC, aka Business Logic Component, is a state management system for Flutter. It's main goal is to separate business logic from the presentation layer. The BLoC handles user actions or any other events and generates new state for the view to render.

Repository #

A Repository to handles data operations. It knows where to get the data from and what API calls to make when data is updated. A Repository can utilize a single data source as well as it can be a mediator between different data sources, such as database, web services and caches.

ViewStateBuilder #

ViewStateBuilder is responsible for building the UI based on the view state. It's a wrapper over the BlocBuilder widget so it accepts a bloc object and a set of handy callbacks, which corresponds to each possible state:

  • onReady - informs the presentation layer that view is in it's initial state, and no action has taken place yet,
  • onLoading - informs the presentation layer that the data is being loaded, so it can display a loading indicator,
  • onRefreshing - informs the presentation layer that the data is being refreshed, so it can display a refresh indicator or/and the current state of list elements,
  • onSuccess - informs the presentation layer that the loading is completed and a nonnull and not empty data was retrieved,
  • onEmpty - informs the presentation layer that the loading is completed, but null or empty data was retrieved,
  • onError - informs the presentation layer that the loading or refreshing has ended with an error. It also provides an error that has occurred.

Features #

ListBloc #

The most basic use case. Allows to fetch, refresh and display a list of elements without filtering and pagination. Thus, ListBloc should be used only with a reasonable amount of data. ListBloc provides the methods for loading and refreshing data: loadElements() and refreshElements(). The former is most suitable for initial data fetch or for retry action when the first fetch fails. The latter is designed for being called after the initial fetch succeeds. It can be performed when the list has already been loaded. To display the current view state ListBloc cooperates with BlocBuilder as well as ViewStateBuilder.

ListRepository #

A ListRepository implementation should provide only one method:

Future<List<T>> getAll(); - this method is responsible for providing all the data to the ListBloc.

Where:

  • T is the element type returned by this repository.
Usage #
  1. Create ListBloc using BlocProvider or any other DI framework:
BlocProvider(
    builder: (context) => ListBloc<Post>(PostListRepository()),
    child: PostsPage(),
);
  1. Trigger loading data. Typically it can be done in the StatefulWidget's initState method:
listBloc = BlocProvider.of<ListBloc<Post>>(context)..loadElements();
  1. Use the ViewStateBuilder to build the UI and provide builder functions for states you want to handle:
@override
Widget build(BuildContext context) {
    return ViewStateBuilder(
        bloc: listBloc,
        onLoading: (context) => LoadingIndicator(),
        onSuccess: (context, posts) => PostsList(posts),
        onError: (context, error) => ErrorMessage(error),
    );
}
See also #

List BLoC Sample App

FilterListBloc #

An extension to the ListBloc that allows filtering.

FilterRepository #

FilterListRepository provides two methods:

Future<List<T>> getAll(); - this method is called when a null filter is provided and should return all elements, Future<List<T>> getBy(F filter); - this method is called with nonnull filter and should return only elements that match it.

Where:

  • T is the element type returned by this repository,
  • F is the filter type, which can be primitive as well as complex object.

Usage #

  1. Create FilteredListBloc using BlocProvider or any other DI framework:
BlocProvider(
    builder: (context) => FilteredListBloc<Post>(PostFilterListRepository()),
    child: PostsPage(),
);
  1. Trigger loading data with the initial filter value. Typically it can be done in the StatefulWidget's initState method. The filter parameter is optional and when it's not provided all elements from the repository will be fetched.
filteredListBloc = BlocProvider.of<FilteredListBloc<Post>>(context)..loadElements(filter: filter);
  1. Use the ViewStateBuilder to build the UI and provide builder functions for states you want to handle:
@override
Widget build(BuildContext context) {
    return ViewStateBuilder(
        bloc: listBloc,
        onLoading: (context) => LoadingIndicator(),
        onSuccess: (context, posts) => PostsList(posts),
        onError: (context, error) => ErrorMessage(error),
    );
}
See also #

Filter List BLoC Sample App

PagedListBloc #

A list BLoC with pagination but without filtering. It works best with Infinite Widgets but a custom presentation layer can provided as well.

Page #

Contains information about the current page, this is number and size.

PagedList #

List of elements with information if there are more elements or not.

PagedListRepository #

PagedListRepository comes with only one method:

Future<List<T>> getAll(Page page); - this method retrieves elements meeting the pagination restriction provided by the page object. When elements are exceeded it should return an empty list or throw PageNotFoundException. PagedListBloc will handle both cases in the same way.

Where:

  • T is the element type returned by this repository.

Usage #

  1. Create PagedListBloc using BlocProvider or any other DI framework:
BlocProvider(
    builder: (context) => PagedListBloc<Post>(PostPagedRepository()),
    child: PostsPage(),
);
  1. Trigger loading first page. Typically it can be done in the StatefulWidget's initState method. This is the place, where you can set the page size. Once it is set, it cannot be changed.
listBloc = BlocProvider.of<PagedListBloc<Post>>(context)..loadFirstPage(pageSize: 10);
  1. Use the ViewStateBuilder to build the UI and provide builder functions for states you want to handle:
@override
Widget build(BuildContext context) {
    return ViewStateBuilder(
        bloc: listBloc,
        onLoading: (context) => LoadingIndicator(),
        onSuccess: (context, pagedPosts) => PostsInfiniteList(pagedPosts),
        onError: (context, error) => ErrorMessage(error),
    );
}
  1. For building the presentation layer you can use InfiniteListView or InfiniteGridView which are part of Infinite Widgets library. Thanks to this you can easilly apply the Page properties straight into the InfiniteListView with no additional work required.
InfiniteListView(
    itemBuilder: (context, index) => PostListItem(page.elements[index]),
    itemCount: page.elements.length,
    hasNext: page.hasMoreElements,
    nextData: listBloc.loadNextPage,
    loadingWidget: PageLoadingIndicator(),
);
See also #

Paged List BLoC Sample App

PagedListFilterBloc #

A list BLoC with pagination and filtering. It works best with Infinite Widgets but a custom presentation layer can provided as well.

Page #

Contains information about the current page, this is number and size.

PagedList #

List of elements with information if there are more elements or not.

PagedListFilterRepository #

PagedListFilterRepository provides only two methods:

Future<List<T>> getAll(Page page); - retrieves elements meeting the pagination restriction provided by the page object. Future<List<T>> getBy(Page page, F filter); - retrieves elements meeting pagination as well as the filter restrictions provided by the page and filter objects.

When elements are exceeded it should return an empty list or throw PageNotFoundException. PagedListFilterBloc will handle both cases in the same way.

Where:

  • T is the element type returned by this repository,
  • F is the filter type, which can be primitive as well as complex object.

Usage #

  1. Create PagedListFilterBloc using BlocProvider or any other DI framework:
BlocProvider(
    builder: (context) => PagedListFilterBloc<Post>(PostPagedFilterRepository()),
    child: PostsPage(),
);
  1. Trigger loading first page. Typically it can be done in the StatefulWidget's initState method. This is the place, where you can set the page size. Once it is set, it cannot be changed.
listBloc = BlocProvider.of<PagedListFilterBloc<Post>>(context)..loadFirstPage(pageSize: 10, filter: filter);
  1. Use the ViewStateBuilder to build the UI and provide builder functions for states you want to handle:
@override
Widget build(BuildContext context) {
    return ViewStateBuilder(
        bloc: listBloc,
        onLoading: (context) => LoadingIndicator(),
        onSuccess: (context, pagedPosts) => PostsInfiniteList(pagedPosts),
        onError: (context, error) => ErrorMessage(error),
    );
}
  1. For building the presentation layer you can use InfiniteListView or InfiniteGridView which are part of Infinite Widgets library. Thanks to this you can easilly apply the Page properties straight into the InfiniteListView with no additional work required.
InfiniteListView(
    itemBuilder: (context, index) => PostListItem(page.elements[index]),
    itemCount: page.elements.length,
    hasNext: page.hasMoreElements,
    nextData: listBloc.loadNextPage,
    loadingWidget: PageLoadingIndicator(),
);
See also #

Paged List BLoC Sample App

DetailsBloc #

A BLoC that allows to fetch a single element with given identifier.

DetailsRepository #

DetailsRepository comes with only one method:

Future<T> getById(I id); - this method retrieves an element with given id. When there's no element matching the id the null should be returned or ElementNotFoundException should be thrown. In both cases the DetailsBloc will emit Empty state.

Where:

  • T is the element type returned by this repository,
  • I is the id type, it can be primitive as well as a complex object.

Usage: #

  1. Create DetailsBloc using BlocProvider or any other DI framework:
BlocProvider(
    builder: (context) => DetailsBloc<PostDetails, int>(PostDetailsRepository()),
    child: PostDetailsPage(settings.arguments as int),
);
  1. Trigger loading the element. Typically it can be done in the StatefulWidget's initState method.
listBloc = BlocProvider.of<DetailsBloc<PostDetails, int>>(context)..loadElement(widget.id);
  1. Use the ViewStateBuilder to build the UI and provide builder functions for states you want to handle:
@override
Widget build(BuildContext context) {
    return ViewStateBuilder(
        bloc: detailsBloc,
        onLoading: (context) => LoadingIndicator(),
        onSuccess: (context, post) => PostDetailsContent(post),
        onError: (context, error) => ErrorMessage(error),
    );
}
See also #

List/Details BLoC Sample App

Dart version #

  • Dart 2: >= 2.2.0

Author #

[0.3.1]

  • ViewState exported.

[0.3.0]

  • PagedFilterListBloc - a list BLoC with pagination and filtering,
  • PagedRepository renamed to PagedListRepository.
  • FilterRepository renamed to FilterListRepository.
  • Migrating to bloc 2.0.0 and flutter_bloc 2.0.1

[0.2.2]

  • Migrating to bloc 1.0.0 and flutter_bloc 1.0.0

[0.2.1]

  • Migrating to bloc 0.16.1 and flutter_bloc 0.22.0

[0.2.0]

  • Initial state introduced along with onReady callback for the ViewStateBuilder,
  • Repository renamed to ListRepository,

[0.1.1]

  • Formatting issues fixed.

[0.1.0]

  • ListBloc - a basic list BLoC with no filtering nor pagination,
  • FilterListBloc - a list BLoC with filtering, but without pagination,
  • PagedListBloc - a list BLoC with pagination but without filtering,
  • DetailsBloc - a BLoC that allows to fetch a single element with given identifier.

example/README.md

BLoC Pattern Samples #

Samples illustrating the usage of BLoC Patterns.

Samples #

  1. List BLoC.
  2. Filter List BLoC.
  3. Paged List BLoC.
  4. Paged Filter List BLoC.
  5. Details BLoC.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  flutter_bloc_patterns: ^0.3.1

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_bloc_patterns/base_list.dart';
import 'package:flutter_bloc_patterns/details.dart';
import 'package:flutter_bloc_patterns/filter_list.dart';
import 'package:flutter_bloc_patterns/paged_filter_list.dart';
import 'package:flutter_bloc_patterns/paged_list.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
71
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
86
Learn more about scoring.

We analyzed this package on Dec 10, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.1
  • pana: 0.13.1+4
  • Flutter: 1.12.13+hotfix.2

Health suggestions

Format lib/src/common/view_state_builder.dart.

Run flutter format to format lib/src/common/view_state_builder.dart.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.2.0 <3.0.0
bloc ^2.0.0 2.0.0 3.0.0-dev.1
equatable ^1.0.1 1.0.1
flutter 0.0.0
flutter_bloc ^2.0.1 2.1.1 3.0.0-dev.1
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.8
provider 3.2.0 4.0.0-dev
rxdart 0.22.6 0.23.0-dev.3
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
bloc_test ^2.2.1
flutter_test
mockito ^4.1.1