BLoC is an acronym for business logic components. The Flutter BLoC package makes it easy to implement the bloc pattern recommended by Google (Google I/O 2018).

Why Bloc?

Bloc makes it easy to separate presentations from business logic, making your code fast, easy to test, and reusable.

When building production quality applications, managing state becomes critical.

As developers we want to:

1. know what state our application is in at any point in time.

2. easily test every case to make sure our app is responding appropriately.

3. record every single user interaction in our application so that we can make data-driven decisions.

4. work as efficiently as possible and reuse components both within our application and across other applications.

5. have many developers seamlessly working within a single code base following the same patterns and conventions.

6. develop fast and reactive apps.

Bloc was designed with three core values in mind:

1. Simple: Easy to understand & can be used by developers with varying skill levels.

2. Powerful: Help make amazing, complex applications by composing them of smaller components.

3. Testable: Easily test every aspect of an application so that we can iterate with confidence.

There are four main layers of application in the BLoC pattern:

UI: The UI contains all of the application’s components that the user can see and interact with.

BLoC: The bloc is the layer between the data and the UI components. The bloc receives events from an external source and emits a state in response to the received event.

Repository: The repository is designed to be the single source of truth, it is responsible for organizing data from the data source(s) to be presented by the UI.

Data providers: The data providers are responsible for fetching the application data, they are characterized by network calls and database interactions.

The BLoC pattern relies on two main components, presented below.

Events: Events are input to a bloc, they’re usually added as a result of user activities like button pushes, or lifecycle events like page loads. You can model your event as anything, from a primitive data type, such as an integer, to any complex abstracted classes.

abstract class AuthEvent {}

class signInEvent extends AuthEvent {}

class LogoutEvent extends AuthEvent {}


States: States are an output of a bloc, they represent the application state. The UI components listen to a state change and redraw a portion of themselves based on the current state. The state can also be modeled as anything from a primitive data type, such as an integer, to any complex abstracted classes.

abstract class AuthState {}

class SignInSuccessfulState extends AuthState {}

class SignInFailedState extends AuthState {}

Bloc: A bloc uses an event to trigger a state change. Blocs are event receivers that turn incoming events into outgoing states.

Bloc widgets

BlocBuilder

BlocBuilder is a Flutter development widget that requires a bloc and a builder function. BlocBuilder handles building the widget in response to new states. BlocBuilder is very similar to StreamBuilder but has a more simple API to reduce the amount of boilerplate code needed. The builder function will potentially be called many times and should be a pure function that returns a widget in response to the state.

If the bloc parameter is omitted, BlocBuilder will automatically perform a lookup using BlocProvider and the current BuildContext.

BlocBuilder<AuthBloc, AuthState>(

buildwhen: (previousState, state) {

    // return true/false to determine whether or not

    // to rebuild the widget with state  },

  bloc: AuthBloc, // provide the local bloc instance

  builder: (context, state) {

    // return widget here based on AuthBloc's state

  }

)

BlocSelector

BlocSelector is a Flutter widget that is analogous to BlocBuilder but allows developers to filter updates by selecting a new value based on the current bloc state. Unnecessary builds are prevented if the selected value does not change. The selected value must be immutable in order for BlocSelector to accurately determine whether the builder should be called again.

If the bloc parameter is omitted, BlocSelector will automatically perform a lookup using BlocProvider and the current BuildContext.

BlocSelector<AuthBloc, AuthState, SelectedState>(

  selector: (state) {

    // return selected state based on the provided state.

  },

  builder: (context, state) {

    // return widget here based on the selected state.

  },

)

BlocProvider

BlocProvider is a Flutter widget that provides a bloc to its children via BlocProvider.of(context). It is used as a dependency injection (DI) widget so that a single instance of a bloc can be provided to multiple widgets within a subtree.

In most cases, BlocProvider should be used to create new blocs which will be made available to the rest of the subtree. In this case, since BlocProvider is responsible for creating the bloc, it will automatically handle closing it.

BlocProvider(

  create: (BuildContext context) => BlocA(),

  child: ChildA(),

);

By default, BlocProvider will create the bloc lazily, meaning create will get executed when the bloc is looked up via BlocProvider.of(context).

To override this behavior and force create to be run immediately, lazy can be set to false.

BlocProvider(

  lazy: false,

  create: (BuildContext context) => BlocA(),

  child: ChildA(),

);

In some cases, BlocProvider can be used to provide an existing bloc to a new portion of the widget tree. This will be most commonly used when an existing bloc needs to be made available to a new route. In this case, BlocProvider will not automatically close the bloc since it did not create it.

BlocProvider.value(

  value: BlocProvider.of(context),

  child: ScreenA(),

);

then from either ChildA, or ScreenA we can retrieve BlocA with:

// with extensions

context.read();

// without extensions

BlocProvider.of(context);

The above snippets result in a one time lookup and the widget will not be notified of changes. To retrieve the instance and subscribe to subsequent state changes use:

// with extensions

context.watch();

// without extensions

BlocProvider.of(context, listen: true);

In addition, context.select can be used to retrieve part of a state and react to changes only when the selected part changes.

final isPositive = context.select((CounterBloc b) => b.state >= 0);

The snippet above will only rebuild if the state of the CounterBloc changes from positive to negative or vice versa and is functionally identical to using a BlocSelector.

MultiBlocProvider

MultiBlocProvider is a Flutter widget that merges multiple BlocProvider widgets into one. MultiBlocProvider improves the readability and eliminates the need to nest multiple BlocProviders.

MultiBlocProvider(

  providers: [

    BlocProvider(

      create: (BuildContext context) => BlocA(),

    ),

    BlocProvider(

      create: (BuildContext context) => BlocB(),

    ),

    BlocProvider(

      create: (BuildContext context) => BlocC(),

    ),

  ],

  child: ChildA(),

)

BlocListener

BlocListener is a Flutter widget which takes a BlocWidgetListener and an optional bloc and invokes the listener in response to state changes in the bloc. It should be used for functionality that needs to occur once per state change such as navigation, showing a SnackBar, showing a Dialog, etc...

listener is only called once for each state change (NOT including the initial state) unlike builder in BlocBuilder and is a void function.

If the bloc parameter is omitted, BlocListener will automatically perform a lookup using BlocProvider and the current BuildContext.

BlocListener<BlocA, BlocAState>(

  listener: (context, state) {

    // do stuff here based on BlocA's state

  },

  child: Container(),

)

Only specify the bloc if you wish to provide a bloc that is otherwise not accessible via BlocProvider and the current BuildContext.

BlocListener<BlocA, BlocAState>(

  bloc: blocA,

  listener: (context, state) {

    // do stuff here based on BlocA's state

  }

)

For fine-grained control over when the listener function is called an optional listener can be provided. the listener takes the previous bloc state and current bloc state and returns a boolean. If listener returns true, the listener will be called with the state. If the listener returns false, the listener will not be called with the state.

BlocListener<BlocA, BlocAState>(

  listenWhen: (previousState, state) {

    // return true/false to determine whether or not

    // to call listener with state

  },

  listener: (context, state) {

    // do stuff here based on BlocA's state

  },

  child: Container(),

)

MultiBlocListener

MultiBlocListener is a Flutter widget that merges multiple BlocListener widgets into one. MultiBlocListener improves the readability and eliminates the need to nest multiple BlocListeners.

MultiBlocListener(

  listeners: [

    BlocListener<BlocA, BlocAState>(

      listener: (context, state) {},

    ),

    BlocListener<BlocB, BlocBState>(

      listener: (context, state) {},

    ),

    BlocListener<BlocC, BlocCState>(

      listener: (context, state) {},

    ),

  ],

  child: ChildA(),

)

BlocConsumer

BlocConsumer exposes a builder and listener in order react to new states. BlocConsumer is analogous to a nested BlocListener and BlocBuilder but reduces the amount of boilerplate needed. BlocConsumer should only be used when it is necessary to both rebuild UI and execute other reactions to state changes in the bloc. BlocConsumer takes a required BlocWidgetBuilder and BlocWidgetListener and an optional bloc, BlocBuilderCondition, and BlocListenerCondition.

If the bloc parameter is omitted, BlocConsumer will automatically perform a lookup using BlocProvider and the current BuildContext.

BlocConsumer<BlocA, BlocAState>(

  listener: (context, state) {

    // do stuff here based on BlocA's state

  },

  builder: (context, state) {

    // return widget here based on BlocA's state

  }

)

An optional listenWhen and buildWhen can be implemented for more granular control over when the listener and builder are called. The listenWhen and buildWhen will be invoked on each bloc state change. They each take the previous state and current state and must return a bool which determines whether or not the builder and/or listener function will be invoked. The previous state will be initialized to the state of the bloc when the BlocConsumer is initialized. listenWhen and buildWhen are optional and if they aren't implemented, they will default to true.

BlocConsumer<BlocA, BlocAState>(

  listenWhen: (previous, current) {

    // return true/false to determine whether or not

    // to invoke listener with state

  },

  listener: (context, state) {

    // do stuff here based on BlocA's state

  },

  buildWhen: (previous, current) {

    // return true/false to determine whether or not

    // to rebuild the widget with state

  },

  builder: (context, state) {

    // return widget here based on BlocA's state

  }

)

RepositoryProvider

RepositoryProvider is a Flutter widget that provides a repository to its children via RepositoryProvider.of(context). It is used as a dependency injection (DI) widget so that a single instance of a repository can be provided to multiple widgets within a subtree. BlocProvider should be used to provide blocs whereas RepositoryProvider should only be used for repositories.

RepositoryProvider(

  create: (context) => RepositoryA(),

  child: ChildA(),

);

then from ChildA we can retrieve the Repository instance with:

// with extensions

context.read();

// without extensions

RepositoryProvider.of(context)

MultiRepositoryProvider

MultiRepositoryProvider is a Flutter widget that merges multiple RepositoryProvider widgets into one. MultiRepositoryProvider improves the readability and eliminates the need to nest multiple RepositoryProvider.

MultiRepositoryProvider(

  providers: [

    RepositoryProvider(

      create: (context) => RepositoryA(),

    ),

    RepositoryProvider(

      create: (context) => RepositoryB(),

    ),

    RepositoryProvider(

      create: (context) => RepositoryC(),

    ),

  ],

  child: ChildA(),

)

Testing the BLoC Design Pattern

For testing the bloc, you require two packages:

  • bloc_test
  • flutter_test

Simply inside the test folder, create the app_bloc_test.dart file and start writing test.

Inside we are going to test two conditions:

  • Initial state of application i.e AppState.empty().
  • State changes when the button is pressed.

void main() {

 blocTest(

   'Initial State',

   build: () => AppBlocBloc(),

   verify: (appState) =>

       expect(appState.state, const AppState.empty(), reason: 'Initial State'),

 );

 blocTest(

   'emits [MyState] when MyEvent is added.',

   build: () => AppBlocBloc(),

   act: (bloc) => bloc.add(const ChangeTextEvent()),

   expect: () => const [

     AppState(

       index: 1,

       text: 'Changed Text',

     ),

   ],

 );

}