A library built upon Bloc_Flutter and Provider to make it easy to support async operation
This library includes 4 sub-modules:
The library includes 4 different types of MultiStateResult
, each has a different state to indicates its internal state. The type is an immutable data type, and can be pattern matched like enum.
ActionResult
: A result has 2 states:Completed
andFailed
. It can be used to represents the result of action without return value.AsyncActionResult
: A result has 4 states:Pending
,Waiting
,Completed
, andFailed
. It represents the full lifecycle of an async action without a return value.QueryResult<T>
: A result has 2 states:Succeeded
andFailed
, in whichSucceeded
would hold a value of given typeT
. It represents the result of a query that returns a certain type of data.AsyncQueryResult<T>
: A result has 4, states:Pending
,Waiting
,Suceeded
, andFailed
. It represents a full lifecycle of a query that returns a certain type of data. Potentially, an extra stateDefault
can be used. It is likePending
- indicates the query hasn't been started, but also holds avalue
likeSucceeded
. It can be used to indicate the scenario where the app provides default value as optimistic updates.
Typically, the result can be used to represent the result of an async operation, such as calling API or loading data from DB, or time consuming data processing.
Typically, the result itself should be AsyncActionResult
or AsyncQueryResult
, depends on whether the app cares about the return value or not, and UI would render accordingly, such as loading screen
error screen
or the successful screen
.
The query/action itself would be represented as Future<QueryResult>
/Future<ActionResult>
, which can be used to update AsyncQueryResult
and AsyncActionResult
accordingly.
Typically, AsyncActionResult
would be held by a Bloc
or ValueNotifier
, which will be discussed below.
asAsyncResult
can convert sync result into its async counterpart accordingly.
AsyncActionResult asyncActionResult = actionResult.asAsyncResult();
AsyncQueryResult asyncQueryResult = queryResult.asAsyncResult();
Future.asActionResult
can materialise any future into Future<ActionResult>
. The new future would always fulfill. If the original future fails, it captures the error into a FailedResult
accordingly.
Future<ActionResult>.asActionResult
would flatten the hierarchy and respect the failed value from the original future.
Future<T>.asQueryResult
would materialise the future of T
into Future<QueryResult<T>>
. The new future would always fulfill. If original future fails it captures the error into a FailedResult
accordingly.
MultiStateResult
type provides map
and/or mapOr
method to pattern match and map the value, which can be used to build UI manually.
In practice, ActionResultBuilder
and QueryResultBuilder
provided by this library can be more convenient, with few extra features.
ActionResultBuilder
and QueryResultBuilder
accept different builder function to build the UI according to the state of the AsyncActionResult
or the AsyncQueryResult
.
A simple example:
Widget build(BuildContext context, AsyncActionResult result) =>
ActionResultBuilder(
result: result,
pendingBuilder: (_context) => Center(child: Text("No Data")),
waitingBuilder: (_context) => Center(child: CircularProgressIndicator()),
failedBuilder: (_context, error, _stackTrace) => Center(
child: Text("Error: $error"),
),
completedBuilder: (_context) => Center(child: Text("Completed")),
);
For ActionResultBuilder
and QueryResultBuilder
, builder
is considered as a critical builder, which is mandatory each time the widget is instantiated, while pendingBuilder
, waitingBuilder
, and failedBuilder
are optional and can be omitted.
When the given builder is omitted, the ResultBuilder
will search the widget hierarchy to find any default builder that has been given. Just like Text
would search DefaultTextStyle
if textStyle
is not explicitly given.
Accordingly, DefaultPendingResultBuilder
, DefaultWaitingResultBuilder
, and DefaultFailedResultBuilder
can be used to provide those default builders to their children along the widget tree.
If more than one default builder needs to be configured, rather than nested DefaultXXXResultBuilder
, which results in a relatively ugly code, DefaultResultBuilder
can be used, which allow configuring multiple default builders at once.
DefaultResultBuilder
can be used to set up the unified UI style of a portion of the screen.
If optional builders are omitted and no default builders are given, the ResultBuilder
widget would use something called global default builder
to render the UI.
Which can be set up via
DefaultPendingResultBuilder.globalBuilder
DefaultWaitingResultBuilder.globalBuilder
DefaultFailedResultBuilder.globalBuilder
It also can be configured in batch via DefaultResultBuilder.setGlobalBuilder
Library provided the classes to integrate the [MultiStateResult] to Bloc
This library uses Cubit
instead of Bloc
, which is a simplified and easier-to-use Bloc
.
ActionCubit
: ACubit
holdsAsyncActionResult
, it also provides protected methods to update its value from different kinds ofFuture
QueryCubit<T>
: ACubit
holdsAsyncQueryResult<T>
, it also provides protected methods to update its value from different kinds ofFuture
Both ActionCubit
and QueryCubit
can be provided to the widget tree via BlocProvider
Like BlocBuilder
and BlocConsumer
provided by Bloc_Flutter library, the following types are provided:
ActionBlocBuilder
:BlocBuilder
that consumesActionCubit
viaActionResultBuilder
ActionBlocConsumer
:BlocConsumer
that consumesActionCubit
viaActionResultBuilder
QueryBlocBuilder
:BlocBuilder
that consumesQueryCubit
viaQueryResultBuilder
QueryBlocConsumer
:BlocConsumer
that consumesQueryCubit
viaQueryResultBuilder
Besides Cubit
, this library also provides the integration to ValueNotifier
and ListenableBuilder
.
ActionNotifier
: AValueNotifier
holdsAsyncActionResult
, it also provides protected methods to update its value from different kinds ofFuture
QueryNotifier<T>
: AValueNotifier
holdsAsyncQueryResult<T>
, it also provides protected methods to update its value from different kinds ofFuture
Like ListenableBuilder
, the following classes are provided:
ActionListenableBuilder
:ListenableBuilder
that consumesActionNotifier
viaActionResultBuilder
QueryListenableBuilder
:ListenableBuilder
that consumesQueryNotifier
viaQueryResultBuilder
It could be kind of annoying to build a ListView
/GridView
from a List
, which could be empty
, or to build UI from a field of a JSON that could be null
.
EmptyableContentBuilder
is the widget designed to deal with those cases.
Widget build(BuildContext context) {
return QueryBlocBuilder(
bloc: context.read<MyStringListBloc>(),
builder: (_, list) => EmptyableContentBuilder(
value: list,
emptyBuilder: (_) => Center(child: Text("List is empty")),
builder: (_, list) => ListView.builder(
itemCount: list.length,
itemBuilder: (_, index) => ItemWidget(item: list[index]),
),
);
)
}
EmptyableContentBuilder
supports empty checking for List
Map
Iterable
and String
by default, it treats null
as empty too.
To make BlankableBuilder
work with other types, blankChecker
checker can be provided.