What is Flutter Bloc (Part 1)
State management in Flutter
State management is the process of updating and managing the state of an application. It's a necessity for bigger, more complex projects — without it, code becomes hard to manage and prone to bugs.
Without state management we'd pass data between screens manually and trigger updates manually too, which becomes problematic as the app grows. If you're building an e-commerce app with a product list, product details, and a cart page, you'd be shuttling the same state between screens by hand. And careless use of setState can quietly degrade performance.
Benefits of state management:
- Performance and granular control — tools like Bloc offer fine-grained listening (context.select), rebuilding only the parts of the UI that changed.
- Separation of concerns — business logic lives apart from UI, which improves readability, maintainability, and testability.
- Scaling — a structured approach keeps code maintainable as the app grows in size and complexity.
- Reusability — separating UI from logic lets you reuse state management code across widgets and screens.
What is Bloc and Cubit
Flutter Bloc is a state management pattern. Its purpose is to separate the user interface (presentation) from the underlying logic of the application, making the code easier to test and reuse.
Bloc
With the Bloc pattern we have events, states, and the bloc. Events are actions a user can perform, like clicking a button. State is data that changes over time and affects the UI, like a counter's value. The Bloc is where the business logic lives: events go in, are mapped to new states, and states come out. Bloc uses streams to emit states.
For example: when the user swipes down to refresh, we add a RefreshScreen event; inside the Bloc we fetch the latest data on that event and emit a new state built from it.
Cubit
Cubit is also used for state management and is very similar to Bloc, except it uses functions to emit new states instead of events.
Bloc vs Cubit:
- Cubit uses functions to emit state whereas Bloc uses events.
- Cubit is simpler and relatively easy to understand.
- Cubit has limited capabilities compared to Bloc and may not suit larger, complex applications.
Flutter Bloc widgets
The flutter_bloc package provides widgets for implementing the pattern. The important ones:
BlocBuilder
Requires a Bloc and a builder function; builds UI in response to new states. Omit the Bloc and it looks one up from the current BuildContext. An optional buildWhen(previous, current) gates rebuilds.
BlocBuilder<BlocA, BlocAState>(
buildWhen: (previousState, state) {
// 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
}
)BlocSelector
Filters updates by selecting a slice of the Bloc's state — no rebuild unless the selected value changes.
BlocSelector<BlocA, BlocAState, SelectedState>(
selector: (state) {
// return selected state based on the provided state.
},
builder: (context, state) {
// return widget here based on the selected state.
},
)BlocProvider
Provides a Bloc to a widget's children via BlocProvider.of(context) — dependency injection for Blocs. It creates the Bloc (lazily by default) and closes it automatically. BlocProvider.value passes an existing Bloc to another subtree; MultiBlocProvider merges several providers without nesting.
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);BlocListener
Invokes a listener once per state change — use it for one-shot side effects like navigation or snackbars. listenWhen fine-tunes when it runs; MultiBlocListener merges several without nesting.
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)BlocConsumer
Exposes a listener and a builder together — equivalent to a nested BlocListener + BlocBuilder with less boilerplate, including optional buildWhen and listenWhen.
RepositoryProvider
Provides a repository instance to a subtree via RepositoryProvider.of(context); MultiRepositoryProvider merges several.
Extension methods
- context.read — equivalent to BlocProvider.of<T>(context); mostly used to add events in callbacks.
- context.watch — listens to changes on the instance; only usable inside build methods, and at the root of a build it rebuilds the whole widget on every state change.
- context.select — listens to changes in a smaller part of the state, e.g. final name = context.select((ProfileBloc bloc) => bloc.state.name).
Bloc architecture
The bloc library encourages dividing the application into three layers, each with its own responsibility:
- Presentation layer — renders itself based on one or more blocs.
- Business logic layer — responds to input from the UI with new states, depending on one or more repositories.
- Data layer — retrieves and manipulates data; further split into data providers (raw data) and repositories (wrappers around one or more providers that the bloc talks to).
How does it all connect? The user taps Login on a page rendered by the presentation layer. The page depends on AuthBloc; the tap fires an event handled by AuthBloc, which passes the login request to the auth repository. If we're using Firebase auth, the repository depends on a Firebase data provider that makes the actual API call. Structured this way, everything is testable and reusable — the Firebase data provider could become a package used by other apps.
Implementing Bloc in Flutter
Continued in Part 2, where we build a small app with all of this.
Originally published on Medium.