BLOC architecture in Flutter made easy.

Mithun Adhikari
6 min readJun 12, 2021

Without telling you anything about the importance of Architecture and design pattern in apps, I will straight dive into the implementation of BLOC architecture in Flutter apps.

The image is taken from the official flutter_bloc library’s GitHub content. This image belongs to https://github.com/felangel/bloc/tree/master/packages/flutter_bloc

Goal

Our goal is here is to learn the basics of BLOC architecture. We will do that by implementing BLOC in the Counter app. In this article, we will be using two libraries flutter_bloc library. And I assume you have basic knowledge of Flutter, such as onPress, events, abstract class, etc. Just a side note, this is beginner content and is useful who are trying to learn BLOC architecture.

Implementation

We will be re-writing the Counter app, generated when you create a flutter app. You can find the source code of this generated counter app here too. The generated code contains three classes, App, MyApp, and _MyAppState. Now since we will be using BLOC architecture, I will be fusing the _MyAppState class into the MyApp class making it a stateless widget, for we will handle the state with BLOC. And also, for now, I will be removing the Floating Action button onPress event, we will handle that part later. Also, I will add one more floating action button, one for incrementing the counter and another one for decrementing the counter. Now the code will look something like this.

If you run the above code, the output will be something like this.

BLOC

Now, to integrate the BLOC architecture in our project, we will add those two libraries in our pubspec.yaml file, like this.

flutter_bloc: ^7.0.1

Now, to separate the business logic and UI logic, let’s create a folder inside the lib directory called bloc. Inside that folder, let’s create 3 files, namely, counter_state.dart, counter_event.dart and counter_bloc.dart.

Counter State

To store and work on the state, we have created a file counter_state.dart, since the only state in our app is a counter variable of type int. We are going to create just that. Our counter_state.dart will be like this.

class CounterState{
final int count;
const CounterState({this.count = 0});
}

Here, since we are dealing with the counter only, we have a class, an instance variable, and a constructor. Now the default value of the count is 0, if not passed in the constructor.

Counter Event

To handle different types of events in our app, we create an event file and put all of the possible events in there. Our counter app only has an increment event and decrement event, so we will have three classes: a base event class and two sub-class extending the base event class. Our counter-event class will be like this.

import ‘package:flutter/material.dart’;@immutable
abstract class CounterEvent{
const CounterEvent();
}
class IncrementEvent extends CounterEvent{}
class DecrementEvent extends CounterEvent{}

Here we will work with IncrementEvent when the plus button is pressed and DecrementEvent when the minus button is pressed.

Counter Bloc

Our counter bloc class will have all the business logic, to be precise, it will be responsible for incrementing and decrementing the counter state. It will receive the event of type CounterEvent defined earlier and emit or yield value of type CounterState defined earlier. Whenever it sees an event of type IncrementEvent it will increase the current state by 1 and vice-versa. This class will need to extend the Bloc class coming from the flutter_bloc package and we will provide the incoming type and outgoing type. The final code will be something like this. Notice, counter_bloc is our app name. Your’s might be different, so handle the imports accordingly.

import 'package:counter_bloc/src/bloc/counter_event.dart';
import 'package:counter_bloc/src/bloc/counter_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CounterBloc extends Bloc<CounterEvent,CounterState>{
CounterBloc() : super(CounterState(count: 0));
@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
if(event is IncrementEvent){
yield(CounterState(count: state.count+1));
}
if(event is DecrementEvent){
yield(CounterState(count: state.count-1));
}
}
}

Here we are extending the Bloc class with an incoming type of CounterEvent and an outgoing type of CounterState. Also, we defined a constructor which calls the superclass with a count of 0. We are required to override the mapEventToState method, as the name suggests, it will map our events with the state. Notice that this a generator function, (notice the async* keyword), and will generate CounterState, as defined in the return type, and since it is a generated function, it will continuously generate CounterState and does not return anything. Inside the function, we are checking if the incoming event is IncrementEvent of DecrementEvent. And inside the if block, we are accessing the current state with a state variable, the state variable is a getter that gives us the current state the CounterBloc is storing. Now to increment, we simply access the current state and increase it by 1. Since our State is a CounterState class, we are yielding the same type.

MyApp Class

Now we are done with the architectural stuff, it’s time to turn our attention to implement that into our view. We will first begin by wrapping our MyHomePage class with BlocProvider. BlocProvider will give access to the bloc to the children of MyApp, no matter how far down the tree the consumer is and no matter the location of that file is. It’s just that awesome. But one thing to notice is that we cannot access the bloc above the widget tree than it is defined. For e.g, we cannot access the bloc in the MyApp widget. The code after wrapping the MyApp with BlocProvider will be like this.

class App extends StatelessWidget{
@override
Widget build(BuildContext context){
return MaterialApp(
title: “Flutter BLOC intro”,
home: BlocProvider(
create: (BuildContext context) {
return CounterBloc();
},
child: MyHomePage(title:"Flutter BLOC intro")) ,
);
}
}

In the BlocProvider, we have to return our bloc from the named parameter which gets called with the build context. Here we are simply returning our CounterBloc. The next step will be to consume the bloc data and pass the events to change the state.

MyHomePage Class

Now that we are provided with the CounterBloc to this class, thanks to the BlocProvider, we can make use of that bloc. The CounterBloc will be accessible from anywhere in the MyHomePage class and any of its children. One of the methods to access the CounterBloc is:

final bloc = BlocProvider.of<CounterBloc>(context);

Now, in our onPress callbacks of two floating action buttons, we will add corresponding events to our bloc. So the implementation will look something like this.

floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
bloc.add(IncrementEvent());
},
tooltip: 'Decrement',
child: Icon(Icons.add),
),
SizedBox(width: 12,),
FloatingActionButton(
onPressed: () {
bloc.add(DecrementEvent());
},
tooltip: 'Decrement',
child: Icon(Icons.remove),
),
],
),

Here you can see we are adding the events in our bloc. When we add events such as IncrementEvent, the mapEventToState method will be called and that will be responsible to handle the state. Now next part is to observe the state change made by the floating action buttons. For that, we will wrap just the part which needs to update itself when the state changes, with BlocBuilder. The content wrapped by BlocBuilder will be re-created when there is a change made by the bloc file. The code will be something like this.

body: BlocBuilder<CounterBloc,CounterState>(
builder: (context, state) {
return Container(
padding: EdgeInsets.all(32),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("You have pressed this button",style: Theme.of(context).textTheme.headline6,),
Text("${state.count} times",style: Theme.of(context).textTheme.headline6),
],
)),
);
}
),

Now you see we are only wrapping the body with BlocBuilder. We can even go further and wrap only the second text part with BlocBuilder. BlocBuilder type will be the bloc we use to add events and want to listen to and the type of data we want to listen to. So in our case, it is CounterBloc and CounterState. And that’s it. That’s our counter app implemented with BLOC architecture. If you run the code, the output will be the same as the generated counter app. But we have lifted the business logic into a separate file. Our MyHomePage does not know anything about the state changes, or how it is being handled, likewise CounterBloc does not know anything about how the state change is being consumed in the UI. That’s the power of BLOC.

Our final main. dart’s code will be this one.

I am Mithun Adhikari, I am a software engineer, Flutter enthusiast, instructor, entrepreneur. You can write me back in case of any query. I will try to communicate back as soon as I can. You can find my contact details and more about me on my portfolio here. If you have any suggestion or anything feel free to write to me. Happy Coding.

--

--