This package, bloc_toolkit
, provides a complete set of tools for efficient and flexible state management in Flutter
apps
using the Bloc pattern. It is designed to simplify app development with Bloc,
offering advanced features for loading, reloading, updating, and initializing data.
The DataBloc
class is the core of the package. It is a generic class that provides a complete set of tools for
managing the state of a data source. It is designed to be used as a base class for implementing business logic in the
application.
class AnimalBloc extends DataBloc<String, String> {
AnimalBloc({required AnimalRepository animalRepository})
: _animalRepository = animalRepository;
final AnimalRepository _animalRepository;
@override
FutureOr<String> loadData(DataS<String> oldState, LoadDataE<String> event) {
return _animalRepository.getAnimal(event.params!);
}
}
BlocProvider(
create: (_) => AnimalBloc(animalRepository: AnimalRepository()),
child: BlocConsumer<AnimalBloc, DataS<String>>(
listener: (context, state) {
if (state is ErrorS<String>) {
_showSnackBar(context, 'Loading animal error: ${state.error}');
}
},
builder: (context, state) {
if (state is UnloadedDataS<String>) {
return ...
}
if (state is LoadingDataS<String>) {
return ...
}
if (state is LoadedDataS<String, String>) {
return ...
}
if (state is ReloadingDataS<String, String>) {
return ...
}
return ...
},
),
)
final animalBloc = context.read<AnimalBloc>();
animalBloc.add(const LoadDataE(params: 'some args'));
//or
animalBloc.add(InitializeDataE('dog'))
...
//then you can
animalBloc.add(const ReloadDataE(params: 'some args'))
// or
animalBloc.add(UpdateDataE((currentData) => 'cat'));
DataBlos
has the following states:
abstract DataS:
The base state for all states.abstract IdleS:
The base state when nothing is happening.abstract LoadingS:
The base state when data is loading or reloading.abstract ErrorS:
The base state when there is an error.
abstract UnloadedS:
The base state for all unloaded states.UnloadedDataS:
The initial state when no data is loaded.LoadingDataS:
The state when data is being loaded.LoadingDataErrorS:
The state when a data loading error occurred.
abstract LoadedS:
The base state for all loaded states.LoadedDataS:
The state when data has been successfully loaded or initialized successfullyReloadingDataS:
The state when data is being reloaded.ReloadingDataErrorS:
The state when a data reload error occurred.
The DataBloc class can handle the following events:
LoadDataE:
Event for initial data loading.InitializeDataE:
Event to initialize data without loading.ReloadDataE:
Event to reload data when it has already been loaded or initialized.UpdateDataE:
Event to update data when it is already loaded or initialized.
By default, all errors thrown during data loading are converted to DataException which can be received in
LoadingDataErrorS or ReloadingDataErrorS states. Therefore, they will not get into BlocObserver.onError
,
but you can handle them in BlocObserver.onChange
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
final nextState = change.nextState;
if (nextState is ErrorS) {
final error = nextState.error;
if (error is UnhandledDataException) {
_logger.f('UnhandledDataException',
error: error.error, stackTrace: error.stackTrace);
//TODO: send to analytics
}
}
}
If you want to handle errors in BlocObserver.onError
you should override this behavior using
the overridedOnLoadingError
and overridedOnReloadingError
methods when implementing your DataBloc .
void _$onLoadingError(
DataException error,
UnloadedDataS<String> state,
Emitter<DataS<String>> emit, {
String? params,
}) {
if (error is UnhandledDataException) {
throw error;
}
emit(LoadingDataErrorS(error, params: params));
emit(const UnloadedDataS());
}
void _$onReloadingError(
DataException error,
LoadedS<String, String> state,
Emitter<DataS<String>> emit, {
String? params,
}) {
if (error is UnhandledDataException) {
throw error;
}
emit(ReloadingDataErrorS(state, error, params: params));
emit(LoadedDataS(state.data, params: state.params));
}
class AnimalBloc extends DataBloc<String, String> {
AnimalBloc({
required AnimalRepository animalRepository,
super.overridedOnLoadingError = _$onLoadingError,
super.overridedOnReloadingError = _$onReloadingError,
})
}
Then in BlocObserver.onError
you can handle them
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
super.onError(bloc, error, stackTrace);
if (error is UnhandledDataException) {
final originError = error.error;
final originStackTrace = error.stackTrace;
//send to sentry...
}
}
To properly handle user errors (e.g. http errors) you must implement the DataException
interface and they must be
thrown in repositories. Otherwise all errors will be converted to UnhandledDataException
which implements the DataException
interface.
ListBloc is an extended DataBloc for convenient work with lists with the ability to sort and filter list items.
ListBloc
by default has DateS<List> where T is the type of the element in the list.
ListBloc
has ListParams with a list of filters(FilterPredicate) and a comparator(Comparator) for sorting elements.
ApplyParamsE
extends UpdateDataE and accepts ListParams parameters.
For convenience, the implementation of selecting an item from a list is implemented by SelectBloc
.
It does not extend DataBloc
, but is a regular Bloc
.
SelectBloc
has only two states SelectS
, when the item is not selected, and SelectedS
, if the item is selected.
SelectBloc
can handle only one event SelectE
, which takes the item that should be selected or unselected if null is
passed.