In the following tutorial, we're going to build a Counter in Flutter using the Bloc library.
- Observe state changes with BlocObserver.
- BlocProvider, Flutter widget which provides a bloc to its children.
- BlocBuilder, Flutter widget that handles building the widget in response to new states.
- Using Cubit instead of Bloc. What's the difference?
- Adding events with context.read.⚡
We'll start off by creating a brand new Flutter project
flutter create flutter_counter
We can then go ahead and replace the contents of pubspec.yaml
with
and then install all of our dependencies
flutter packages get
├── lib
│ ├── app.dart
│ ├── counter
│ │ ├── counter.dart
│ │ ├── cubit
│ │ │ └── counter_cubit.dart
│ │ └── view
│ │ ├── counter_page.dart
│ │ └── counter_view.dart
│ ├── counter_observer.dart
│ └── main.dart
├── pubspec.lock
├── pubspec.yaml
The application uses a feature-driven directory structure. This project structure enables us to scale the project by having self-contained features. In this example we will only have a single feature (the counter itself) but in more complex applications we can have hundreds of different features.
The first thing we're going to take a look at is how to create a BlocObserver
which will help us observe all state changes in the application.
Let's create lib/counter_observer.dart
:
In this case, we're only overriding onChange
to see all state changes that occur.
?> Note: onChange
works the same way for both Bloc
and Cubit
instances.
Next, let's replace the contents of main.dart
with:
We're initializing the CounterObserver
we just created and calling runApp
with the CounterApp
widget which we'll look at next.
Let's create lib/app.dart
:
CounterApp
will be a MaterialApp
and is specifying the home
as CounterPage
.
?> Note: We are extending MaterialApp
because CounterApp
is a MaterialApp
. In most cases, we're going to be creating StatelessWidget
or StatefulWidget
instances and composing widgets in build
but in this case there are no widgets to compose so it's simpler to just extend MaterialApp
.
Let's take a look at CounterPage
next!
Let's create lib/counter/view/counter_page.dart
:
The CounterPage
widget is responsible for creating a CounterCubit
(which we will look at next) and providing it to the CounterView
.
?> Note: It's important to separate or decouple the creation of a Cubit
from the consumption of a Cubit
in order to have code that is much more testable and reusable.
Let's create lib/counter/cubit/counter_cubit.dart
:
The CounterCubit
class will expose two methods:
increment
: adds 1 to the current statedecrement
: subtracts 1 from the current state
The type of state the CounterCubit
is managing is just an int
and the initial state is 0
.
?> Tip: Use the VSCode Extension or IntelliJ Plugin to create new cubits automatically.
Next, let's take a look at the CounterView
which will be responsible for consuming the state and interacting with the CounterCubit
.
Let's create lib/counter/view/counter_view.dart
:
The CounterView
is responsible for rendering the current count and rendering two FloatingActionButtons to increment/decrement the counter.
A BlocBuilder
is used to wrap the Text
widget in order to update the text any time the CounterCubit
state changes. In addition, context.read<CounterCubit>()
is used to look-up the closest CounterCubit
instance.
?> Note: Only the Text
widget is wrapped in a BlocBuilder
because that is the only widget that needs to be rebuilt in response to state changes in the CounterCubit
. Avoid unnecessarily wrapping widgets that don't need to be rebuilt when a state changes.
Let's create lib/counter/counter.dart
:
Add counter.dart
to export all the public facing parts of the counter feature.
That's it! We've separated the presentation layer from the business logic layer. The CounterView
has no idea what happens when a user presses a button; it just notifies the CounterCubit
. Furthermore, the CounterCubit
has no idea what is happening with the state (counter value); it's simply emitting new states in response to the methods being called.
We can run our app with flutter run
and can view it on our device or simulator/emulator.
The full source (including unit and widget tests) for this example can be found here.