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

Cubit Architecture

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.

Cubit Flow

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

Bloc Architecture

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.

Bloc Flow

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.

Maintainers #