Flutter library for state management - bloc 9.0.0
A reliable state management library designed to implement the BLoC (Business Logic Component) design pattern.
Discover more at bloclibrary.dev!
This package is specifically crafted to integrate seamlessly with:
Overview
This package aims to simplify the implementation of the BLoC
Design Pattern (Business Logic Component).
The BLoC pattern separates presentation from business logic , promoting testability and reusability. By abstracting the reactive elements, this package enables developers to concentrate on crafting the business logic.
Cubit
A Cubit
is a class that extends BlocBase
and can be used to manage any type of state.
Each Cubit
requires an initial state, which represents the state before emit
is called. The current state of a Cubit
can be accessed using the state
getter, and its state can be updated by invoking emit
with a new value.
State changes in a Cubit
are initiated through predefined function calls that use the emit
method to produce new states.
The onChange
method is triggered just before a state transition and provides access to both the current and next state.
Creating a Cubit
/// A `CounterCubit` which manages an `int` as its state.
class CounterCubit extends Cubit<int> {
/// The initial state of the `CounterCubit` is 0.
CounterCubit() : super(0);
/// When increment is called, the current state
/// of the cubit is accessed via `state` and
/// a new `state` is emitted via `emit`.
void increment() => emit(state + 1);
}
Using a Cubit
void main() {
/// Create a `CounterCubit` instance.
final cubit = CounterCubit();
/// Access the state of the `cubit` via `state`.
print(cubit.state); // 0
/// Interact with the `cubit` to trigger `state` changes.
cubit.increment();
/// Access the new `state`.
print(cubit.state); // 1
/// Close the `cubit` when it is no longer needed.
cubit.close();
}
Observing a Cubit
onChange
can be overridden to observe state changes for a single cubit
.
onError
can be overridden to observe errors for a single cubit
.
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void increment() => emit(state + 1);
@override
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
@override
void onError(Object error, StackTrace stackTrace) {
print('$error, $stackTrace');
super.onError(error, stackTrace);
}
}
BlocObserver
can be used to observe all cubits
.
class MyBlocObserver extends BlocObserver {
@override
void onCreate(BlocBase bloc) {
super.onCreate(bloc);
print('onCreate -- ${bloc.runtimeType}');
}
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('onChange -- ${bloc.runtimeType}, $change');
}
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('onError -- ${bloc.runtimeType}, $error');
super.onError(bloc, error, stackTrace);
}
@override
void onClose(BlocBase bloc) {
super.onClose(bloc);
print('onClose -- ${bloc.runtimeType}');
}
}
void main() {
Bloc.observer = MyBlocObserver();
// Use cubits...
}
Bloc
A Bloc
is a more advanced class that uses events
to trigger state
changes instead of direct function calls.
Like Cubit
, Bloc
also extends BlocBase
, meaning it shares a similar public API. However, instead of calling a function
on a Bloc
to directly emit a new state
, Blocs
listen for incoming events
and transform those events into outgoing states
.
State changes in a Bloc
begin when events are added, triggering the onEvent
method. The events are then processed through an EventTransformer
. By default, each event is handled concurrently, but a custom EventTransformer
can be provided to modify the incoming event stream.
All registered EventHandlers
for a specific event type are then called with the incoming event. Each EventHandler
is responsible for emitting zero or more states in response to the event. Finally, the onTransition
method is invoked just before the state is updated, providing access to the current state, the event, and the next state.
Creating a Bloc
/// The events which `CounterBloc` will react to.
sealed class CounterEvent {}
/// Notifies bloc to increment state.
final class CounterIncrementPressed extends CounterEvent {}
/// A `CounterBloc` which handles converting `CounterEvent`s into `int`s.
class CounterBloc extends Bloc<CounterEvent, int> {
/// The initial state of the `CounterBloc` is 0.
CounterBloc() : super(0) {
/// When a `CounterIncrementPressed` event is added,
/// the current `state` of the bloc is accessed via the `state` property
/// and a new state is emitted via `emit`.
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
}
}
Using a Bloc
Future<void> main() async {
/// Create a `CounterBloc` instance.
final bloc = CounterBloc();
/// Access the state of the `bloc` via `state`.
print(bloc.state); // 0
/// Interact with the `bloc` to trigger `state` changes.
bloc.add(CounterIncrementPressed());
/// Wait for next iteration of the event-loop
/// to ensure event has been processed.
await Future.delayed(Duration.zero);
/// Access the new `state`.
print(bloc.state); // 1
/// Close the `bloc` when it is no longer needed.
await bloc.close();
}
Observing a Bloc
Since all Blocs
extend BlocBase
just like Cubit
, onChange
and onError
can be overridden in a Bloc
as well.
In addition, Blocs
can also override onEvent
and onTransition
.
onEvent
is called any time a new event
is added to the Bloc
.
onTransition
is similar to onChange
, however, it contains the event
which triggered the state change in addition to the currentState
and nextState
.
sealed class CounterEvent {}
final class CounterIncrementPressed extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<CounterIncrementPressed>((event, emit) => emit(state + 1));
}
@override
void onEvent(CounterEvent event) {
super.onEvent(event);
print(event);
}
@override
void onChange(Change<int> change) {
super.onChange(change);
print(change);
}
@override
void onTransition(Transition<CounterEvent, int> transition) {
super.onTransition(transition);
print(transition);
}
@override
void onError(Object error, StackTrace stackTrace) {
print('$error, $stackTrace');
super.onError(error, stackTrace);
}
}
BlocObserver
can be used to observe all blocs
as well.
class MyBlocObserver extends BlocObserver {
@override
void onCreate(BlocBase bloc) {
super.onCreate(bloc);
print('onCreate -- ${bloc.runtimeType}');
}
@override
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
print('onEvent -- ${bloc.runtimeType}, $event');
}
@override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('onChange -- ${bloc.runtimeType}, $change');
}
@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print('onTransition -- ${bloc.runtimeType}, $transition');
}
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('onError -- ${bloc.runtimeType}, $error');
super.onError(bloc, error, stackTrace);
}
@override
void onClose(BlocBase bloc) {
super.onClose(bloc);
print('onClose -- ${bloc.runtimeType}');
}
}
void main() {
Bloc.observer = MyBlocObserver();
// Use blocs...
}
Dart Versions
- Dart 2: >= 2.12
Examples
- Counter - an example of how to create a
CounterBloc
in a pure Dart app.