State Management in Flutter with BloC Pattern

Stuti Goyal
Blockchain Research Lab
6 min readMay 18, 2021

While developing a flutter application you might have listened to state management often and also you must have used it many times. So, have you ever wondered what is it actually? Let’s discuss today!

Well, in simple words, state means data or the current information that a user can see or manipulate.

State development helps you in keeping the code clean, avoiding accruing technical debt and providing an easy way of sharing states through the widgets. State management is a crucial aspect while working on a large-scale production app.

If you’ve been developing Flutter apps, you know it’s crucial to manage the state of your application in the best possible way. Flutter provides us with many state-management options, like Provider, Bloc, Redux, and MobX. But best of all of them is Flutter Bloc which you will get to know why after reading the whole article.

What is BloC actually?

  • It is a pattern created by Google and announced at Google I/0’18. It uses reactive programming to handle data flow within the app and helps developers to separate states from the UI. BloC stands for business logic and component.
  • BloC basically is a box where events come in from one side and states come out from another side or simpler events go in the BloC and states go out from the BLoC.
  • BLoC operates on the events to figure out which state it should output.
  • BLoC contains two main components: Sink and Stream. Both are provided from StreamController which can be accessed from dart:async library.
  • We add streams of data inputs into the Sink and listen for them as stream data output through a Stream.

The Problem

State management can become very messy when the application grows, simple tools like setState in flutter can’t be the solution for managing large changes at once and tools like inherited widget can create a lot of boilerplate code.

Why BloC?

To overcome the above problem we can simply use tools like flutter_bloc/bloc these packages are created by the community are pretty useful for reducing boilerplate code and can be very easy to implement. So, you can take all the boilerplate code as an investment required to keep your code easy to maintain and understand with Bloc.

For example, say you’re building a weather application using Bloc. You’ve defined all of your events and states, and your app is working perfectly.

Now if you have to add another feature within the same screen, you can very simply go ahead and define a new event and a new state inside your BLoC with a small bit of logic on how to map the new event to a new state.Therefore, you don’t have touch/disturb other built events and states, and you can very simply add your new set of features without much of a problem.

So far we have discussed much about BloC, now let’s acknowledge how to implement it using an example.

Implementing the BLoC pattern

  • Installation

For a Dart application, we need to add the bloc package to our pubspec.yaml as a dependency.

dependencies:
bloc: ^7.0.0

For a Flutter application, we need to add the flutter_bloc package to our pubspec.yaml as a dependency.

dependencies:
flutter_bloc: ^7.0.0

Then run flutter packages getin your terminal.

  • Import

Now that we have successfully installed bloc, we can create our main.dart and import bloc.

import 'package:bloc/bloc.dart';Copy to clipboardErrorCopied

For a Flutter application we can import flutter_bloc.

import 'package:flutter_bloc/flutter_bloc.dart';
  • Creating Events
abstract class MovieEvent {}

class MovieCategorySelectedEvent extends MovieEvent {
final String movieCategory;

MovieCategorySelectedEvent(this.movieCategory);
}

As we can see from the code above, we have created an abstract class called MovieEvent which is the base class that all events will inherit. We have one event called MovieCategorySelectedEvent which is fired when the user changes the movie category. This event inherits MovieEvent and has one field of type string called movieCategory.

  • Creating BLoC

Next, we create a BLoC. A BLoC is a simple Dart class.

import ‘dart:async’;

class MovieBloc {


final _movieEventController = StreamController<MovieEvent>();
StreamSink<MovieEvent> get inMovieEvent => _movieEventController.sink;

final _movieCategoryesStateController = StreamController<List<Movie>>();
StreamSink<List<Movie>> get _inMovies => _movieCategoryesStateController.sink;
Stream<List<Movie>> get outMovies => _movieCategoryesStateController.stream;

final _dropDownValueStateController = StreamController<String>();
StreamSink<String> get _inDropDownValue => _dropDownValueStateController.sink;
Stream<String> get outDropDownValue => _dropDownValueStateController.stream;

MovieBloc(){
_movieEventController.stream.listen(_mapEventToState);

}

void _mapEventToState(MovieEvent event){
if(event is MovieCategorySelectedEvent){
_onMovieCategorySelected(event.movieCategory);
}
}

void _onMovieCategorySelected(String newValue) async {
if(newValue != dropdownValue){
dropdownValue = newValue;
_inDropDownValue.add(dropdownValue);

final _response = await _repo.getMoviesForCategory(newValue);
if(_response != null){
_inMovies.add(_response.movies);
}
}
}

void dispose(){
_movieEventController.close();
_movieCategoryesStateController.close();
_dropDownValueStateController.close();
}



}

In our case, the BLoC class is named MovieBloc. It contains 3 StreamController’s, which come from the dart:async library.

The first StreamController is named _movieEventConttroler and exposes only the Sink for events that enter into BloC. In the constructor of MovieBloc we listen for the outputs of _movieEventConttroler.stream and then we map events that come from Stream into an appropriate state in a simple function called _mapEventToState which accepts events of type MovieEvent.

The second StreamController named _movieCategoryesStateController, is used for updating the list of movies when the movie category is changed. We create a private getter for the Sink of this controller with the name _inMovies, and public getter for Stream for this controller with name outMovies.

The third StreamController named _dropDownValueStateController, is for the currently selected movie category. We create a private getter for the Sink of this controller with the name _inDropDownValue, and public getter for Stream for this controller with the name outDropDownValue.

In the dispose() function we close all 3 StreamController’s by calling the close() function on each of them.

  • Creating the UI

To access the data from Streams we use StreamBuilder widget, we pass the Streams to the stream property of StreamBuilder, and we access the stream data in the builder function of the StreamBuilder.

To avoid memory leaks don’t forget to call the dispose() function on _movieBloc inside the dispose() function in widget state.

class _MoviePageState extends State<MoviePage> {
MovieBloc _movieBloc;

_MoviePageState(){
_movieBloc = MovieBloc();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
‘Movie Database’
),
),
body: _getBody(),
);
}

Widget _getBody(){
return Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreamBuilder(
stream: _movieBloc.outMovies,
initialData: null,
builder: (BuildContext context, AsyncSnapshot<List<Movie>> snapshot){
if(snapshot.data != null){
if(snapshot.data.length > 0){
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: snapshot.data.map<Widget>((movie){
return _getViewCell(movie);
}).toList(),
)
);
}

},
),
Expanded(
child: StreamBuilder(
stream: _movieBloc.outDropDownValue,
initialData: _movieBloc.dropdownValue,
builder: (BuildContext context, AsyncSnapshot<String> snapshot){
return Container(
child: Center(
child: DropdownButton<String>(
value: snapshot.data,
elevation: 16,
style: const TextStyle(
color: Colors.black54
),
underline: Container(
height: 2,
color: Colors.black54,
),
onChanged: (newValue)
=> _movieBloc.inMovieEvent.add(MovieCategorySelectedEvent(newValue)),
items: _movieBloc.movieCategories
.map<DropdownMenuItem<String>>((MovieCategory value) {
return DropdownMenuItem<String>(
value: value.value,
child: Text(
value.label
),
);
}).toList(),
)
)
);
},
)
)
],
);
}

@override
void dispose(){
super.dispose();
_movieBloc.dispose();
}



}

You are all set, let’s see the results.

When we run the application we see the list of movies for a selected movie category. By clicking on the dropdown button we can change the movie category and get movies for that selected category.

Key Takeaways

  • BloC is easy to understand & can be used by developers with varying skill levels.
  • It helps in making amazing, complex applications by composing them of smaller components.
  • We can easily test every aspect of an application so that we can iterate with confidence.
  • Bloc makes it easy to separate presentation from business logic, making your code fast, easy to test, and reusable.

BLoC helps to encapsulate the business logic from the UI. Events are fed in as the input to the logic component, and states are generated as the output. It relies heavily on Streams and is often used in conjunction with the Provider for exposing the BLoC to the UI.

--

--