flutter_bloc_patterns 0.2.1 flutter_bloc_patterns: ^0.2.1 copied to clipboard
A set of most common BLoC use cases build on top of flutter_bloc library.
Flutter BLoC patterns #
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 anonnull
and not empty data was retrieved,onEmpty
- informs the presentation layer that the loading is completed, butnull
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
- Create
ListBloc
usingBlocProvider
or any otherDI
framework:
BlocProvider(
builder: (context) => ListBloc<Data>(DataRepository()),
child: DataPage(),
);
- Trigger loading data. Typically it can be done in the
StatefulWidget
'sinitState
method:
listBloc = BlocProvider.of<ListBloc<Data>>(context)..loadElements();
- Use the
ViewStateBuilder
to build the UI and providebuilder
functions for states you want to handle:
@override
Widget build(BuildContext context) {
return ViewStateBuilder(
bloc: listBloc,
onLoading: (context) => _buildIndicator(),
onSuccess: (context, elements) _buildList(elements),
onError: (context, error) => _buildErrorMessage(error),
);
}
See also
FilterListBloc #
An extension to the ListBloc
that allows filtering.
FilterRepository
FilterRepository
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
- Create
FilteredListBloc
usingBlocProvider
or any otherDI
framework:
BlocProvider(
builder: (context) => FilteredListBloc<Data>(DataFilterRepository()),
child: DataPage(),
);
- Trigger loading data with the initial filter value. Typically it can be done in the
StatefulWidget
'sinitState
method. Thefilter
parameter is optional and when it's not provided all elements from therepository
will be fetched.
filteredListBloc = BlocProvider.of<FilteredListBloc<Data>>(context)..loadElements(filter: initialFilter);
- Use the
ViewStateBuilder
to build the UI and providebuilder
functions for states you want to handle:
@override
Widget build(BuildContext context) {
return ViewStateBuilder(
bloc: listBloc,
onLoading: (context) => _buildIndicator(),
onSuccess: (context, elements) _buildList(elements),
onError: (context, error) => _buildErrorMessage(error),
);
}
See also
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.
PagedRepository
PagedRepository
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 hande both cases in the same way.
Where T
is the element type returned by this repository.
Usage
- Create
PagedListBloc
usingBlocProvider
or any otherDI
framework:
BlocProvider(
builder: (context) => PagedListBloc<Data>(DataPagedRepository()),
child: DataPage(),
);
- Trigger loading first page. Typically it can be done in the
StatefulWidget
'sinitState
method. This is the place, where you can set the page size. Once it is set, it cannot be changed.
listBloc = BlocProvider.of<PagedListBloc<Data>>(context)..loadFirstPage(pageSize: 10);
- Use the
ViewStateBuilder
to build the UI and providebuilder
functions for states you want to handle:
@override
Widget build(BuildContext context) {
return ViewStateBuilder(
bloc: listBloc,
onLoading: (context) => _buildIndicator(),
onSuccess: (context, page) _buildInfiniteList(page),
onError: (context, error) => _buildErrorMessage(error),
);
}
- For building the presentation layer you can use
InfiniteListView
orInfiniteGridView
which are part of Infinite Widgets library. Thanks to this you can easilly apply thePage
properties straight into theInfiniteListView
with no additional work required.
InfiniteListView(
itemBuilder: (context, index) => _buildListItem(page.elements[index]),
itemCount: page.elements.length,
hasNext: page.hasMoreElements,
nextData: listBloc.loadNextPage,
loadingWidget: _buildPageLoadingIndicator(),
);
See also
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:
- Create
DetailsBloc
usingBlocProvider
or any otherDI
framework:
BlocProvider(
builder: (context) => DetailsBloc<Data, int>(DataDetailsRepository()),
child: DataDetailsPage(settings.arguments as int),
);
- Trigger loading the element. Typically it can be done in the
StatefulWidget
'sinitState
method.
listBloc = BlocProvider.of<DetailsBloc<Data, int>>(context).loadElement(widget.id);
- Use the
ViewStateBuilder
to build the UI and providebuilder
functions for states you want to handle:
@override
Widget build(BuildContext context) {
return ViewStateBuilder(
bloc: detailsBloc,
onLoading: (context) => _buildIndicator(),
onSuccess: (context, element) _buildElement(element),
onError: (context, error) => _buildErrorMessage(error),
);
}
See also
Dart version #
- Dart 2: >= 2.2.0