Flutter — Bloc/Cubit

Burak Akten
7 min readDec 24, 2022

Hello Flutter lovers 💜 😄 💜

This tutorial will go over how you can use Bloc/Cubit to manage state of your applications. I am planing to tell you about flutter_bloc package and creating a base system for state management by creating simple flutter app which I will not worry about UI. First of all, let’s see what is flutter_bloc shortly.

Before reading this article, you can check on my previous article which is about state management and MVVM architecture by using Provider package.

Flutter Bloc

When we create an mobile application, managing state of the application becomes critical for us. In some point, we need to know what is the state and we may act different for each state. Also we want to separate UI from business logic. These are the places we need a system to handle state. Bloc makes them easy to implement. There are different Bloc libraries on pub.dev but we need to use flutter_bloc package in order to build Flutter application.

In order to use flutter_bloc in our application, we must add like this line into pubspec.yaml file of project.

dependencies:
flutter_bloc: ^x.y.z

Or we can run this command on command line when you in the project folder.

$ flutter pub add flutter_bloc

For more information about flutter_bloc:

After understanding why we use flutter_bloc in our application, let’s start to implementation of the application.

I will implement an application which has one screen two tabs. Tabs are called as Jobs and Accepted Jobs. I will make http call to get some jobs information, then shows these jobs on the Jobs tab. In jobs tab we can accept or reject a job. If we accept the job, job will be seen in Accepted Jobs tab. If we reject then job will be removed from list.

First of all, let’s create base part of the project in lib/base directory.

In base directory, I created states/, views/ and viewmodels/ directories. What I said in the beginning, we need to manage state of application. So let’s add base states of the application in a file which is called states/base_states.dart.

abstract class BaseState {
const BaseState();
}

class BaseInitialState extends BaseState {
const BaseInitialState();
}

class BaseLoadingSate extends BaseState {
const BaseLoadingSate();
}

class BaseCompletedState extends BaseState {
dynamic data;
BaseCompletedState({this.data});
}

class BaseErrorState extends BaseState {
final String? errorMessage;
BaseErrorState({this.errorMessage});
}

Firstly I created an abstract class that called BaseState. After that I created other state classes, by extending BaseState, that I need when build a screen. What are these states? What are their purpose? Let me explain these questions shortly;

BaseInitialState: As you can guess from the name, this is the initial state. When we create a view model, we use this as initial.

BaseLoadingState: We control the busy status of view model with this state. For example, when we make http call, we set state as BaseLoadingState until we get response from call.

BaseCompletedState: When we get response without any error from http call and if we have any data to update UI, we use this state.

BaseErrorState: If we get any error and we want to show error message on UI, we use this state.

These are basic base states of my application. You can add any states if you wish to.

So it is time to talk about BaseViewModel class which is extends Cubit class that is from flutter_bloc package and BaseView which is used this view model to control base state of the widget.

abstract class BaseViewModel extends Cubit<BaseState> {
BaseViewModel() : super(BaseInitialState()) {
load();
}

bool _isLoading = false;
bool _isDisposed = false;

FutureOr<void> _initState;

FutureOr<void> init();

void load() async {
isLoading = true;
_initState = init();
await _initState;
}

//Getters
bool get isLoading => _isLoading;
bool get isDisposed => _isDisposed;

dynamic get data;

//Setters
set isLoading(bool value) {
_isLoading = value;
if (!_isDisposed && _isLoading) emit(BaseLoadingSate());
}

@override
Future<void> close() async {
_isDisposed = true;
super.close();
}
}

I created an abstract BaseViewModel class that will be extended from other viewmodels. In this abstract class, I control and initialize main state of view models and handle main state logic. Actually I manage the main loading state of view model to be used by child view models. Also I created dynamic data getter which is also be used in child view models after being overridden in order to completed state.

class BaseView<T extends BaseViewModel> extends StatelessWidget {
final T Function(BuildContext)? vmBuilder;
final Widget Function(BuildContext, BaseState) builder;
final Function(BuildContext, BaseState) listener;
final Widget Function(BuildContext, BaseErrorState)? errorBuilder;

const BaseView({
Key? key,
required this.vmBuilder,
required this.builder,
required this.listener,
this.errorBuilder,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => vmBuilder!(context),
child: BlocConsumer<T, BaseState>(
listener: listener,
builder: _buildScreenContent,
),
);
}

Widget _buildScreenContent(BuildContext context, BaseState state) => Container(
child: Stack(
children: [
if (state is BaseCompletedState) builder(context, state),
if (state is BaseErrorState)
errorBuilder != null
? errorBuilder!(context, state)
: Center(child: Text("${state.errorMessage ?? "Something wrong."}", style: context.textTheme.bodyText1)),
if ((state is BaseLoadingSate && state.isLoading) || state is BaseInitialState) const Center(child: CircularProgressIndicator.adaptive()),
],
),
);
}

I created a generic type (that must extend our BaseVeiwModel class ) BaseView class which takes a few parameters to draw the view on the screen. Let’s see what this parameters and why we need them?

  • vmBuilder: This is a method that return view model which is relaeted to view, screen or widget.
  • builder: This is also method that is called when drawing the screen after everything is okay.
  • listener: I add this metthot parameter for listening states from child view models. For example, If we want to show dialog or toast message according to a state we can do in this parameter.
  • errorBuilder: This is a method that returns a widget which is called when state is BaseErrorState.

There are several widget in flutter_bloc like BlocListener, BlocSelector, BlocBuilder .. etc. but I preferred BlocProvider and BlocConsumer when I implementing this BaseView because I wanted to implement both listener and builder part in the Base View. I you wish you can see documentation of other from the link that I share with you in the beginning of this document.

Let’s deep into this code base.

BlocProvider(
create: (context) => vmBuilder!(context),
child: BlocConsumer<T, BaseState>(
listener: listener,
builder: _buildScreenContent,
),
)

BlocProvider is a Flutter widget that provides a bloc to its children via BlocProvider.of<T>(context, listen:true) or context.watch<T>(). As you can see it uses Dependency Injection (DI) for view model so that a single instance of a view model (bloc) can be provided to multiple widgets within a subtree. Also by using BlocConsumer, I added the ability of listening state and building screen at the same time. Whenever the state is changed we can do whatever we want in this method when we use Base View to build screen, view, widget.

Now it’s time to use these base parts on the screen.

class JobBoardViewModel extends BaseViewModel {
final JobBoardService service;
JobBoardViewModel(this.service);

List<JobResponse> _jobs = [];
List<JobResponse> _normalJobs = [];
List<JobResponse> _acceptedJobs = [];

@override
Future<void> init() async {
if (!(state is BaseLoadingSate)) emit(BaseLoadingSate());
var response = await service.getJobs();
if (response.isSuccess && response.data != null) {
_jobs = JobResponse.listFromJson(response.data);
_reInitJobs();
} else {
emit(BaseErrorState(errorMessage: response.errorMessage));
}
}

void changeTypeOfJob(int index, JobType type) {
var job = _normalJobs[index];
_normalJobs[index].type = type;
_reInitJobs();
}

void _reInitJobs() {
_normalJobs = _jobs.where((j) => (j.type == JobType.Normal)).toList();
_acceptedJobs = _jobs.where((j) => (j.type == JobType.Accepted)).toList();
emit(BaseCompletedState(data: data));
}

//Getters
@override
Map<JobType, List<JobResponse>> get data => {JobType.Accepted: _acceptedJobs, JobType.Normal: _normalJobs};
}

First of all, I created JobBoardViewModel class by extending BaseViewModel. In this view model I used three states:

  • Before making http call to get jobs I emitted BaseLoadingSate to screen.
  • After successfully getting data, I emitted BaseCompletedState with data which is holding Map as data.
  • Lastly, I emitted BaseErrorState if the http call is unsuccessful.

After creating view model, I created the screen by using BaseView class. I will not fully add screen code here. Just I will add the BaseView part.

I used BaseView with JobBoardViewModel and I give all parameters to see they do their jobs properly.


BaseView<JobBoardViewModel>(
vmBuilder: (context) => JobBoardViewModel(JobBoardService()),
listener: (context, state) => debugPrint(state.runtimeType.toString()),
builder: (context, state) => _buildTabBarView(state as BaseCompletedState),
errorBuilder: (context, state) => TryAgainWidget(errorState: state),
)


Widget _buildTabBarView(BaseCompletedState state) => TabBarView(
controller: _tabController,
children: [
JobsView(jobs: (state.data)[JobType.Normal]),
JobsView(isAccepted: true, jobs: (state.data)[JobType.Accepted]),
],
);

Let’s see the application

That’s all things to tell you about flutter_bloc and creating base state system with bloc/cubit for now. You can see all code from that repo:

Conclusion:

In this article, I wanted to show you flutter_bloc/cubit and how to use them clear and base way in your applications.

They will make your coding life interesting and clear. They are not confusing that you think.

Do not hesitate to get in touch with me about any Flutter things☺️

I hope this article useful for you. Thank you for your time. If you liked this article please leave some 👏 and share it with friends. 🤩

Wait for another topic from me Flutter lovers 💜 😄 💜

Happy coding times 🥳🤩

Get in touch on LinkedIn profile.

Burak Akten, Flutter Developer at Profe Information Systems👈

--

--