Flutterexperts

Empowering Vision with FlutterExperts' Expertise
State Management using Flutter BLoC using Dio and Retrofit

Hello folks!, If you are a Flutter Developer you could have better or bitter experience while developing apps that require you to manage states as well. In flutter, there are various techniques for state management some of them are:

  1. setState()
  2. Inherited widget & Inherited Model
  3. Provider & Scoped Model
  4. Redux
  5. BloC/Rx
  6. MobX
  7. Flutter_BLoc

As a flutter developer, I have experienced that all techniques are best but all have their benefits while flutter_bloc contains important and valuable things from other state management techniques mentioned above.

Why Bloc?

When we are building production quality application state management becomes a critical issue and being a developer we only want to know a few things and these are

  1. How states are changing.
  2. How an application is making interaction that actually helps to make data-driven decisions.
  3. Is the developer able to work with a single code base following the same pattern?
  4. fast and reactive apps.

and it is designed with three core values in mind and these are simplicity, powerfulness, and testability.

Before working with it we need to understand what its inner widgets do and how we manage our data with, Now firstly we have to get the basic knowledge of flutter_bloc.

Basic design patterns are used to make the project well managed, responsible, scalable, and optimized so the flutter_bloc does.

Implementation:

Step 1: Add the dependencies

Add dependencies to pubspec.yaml file.

dependencies:

flutter_bloc: ^latest version

Step 2: Import

import ‘package:flutter_bloc/flutter_bloc.dart’;

this is the installation flutter_bloc now let’s move to understand

core concepts of the bloc.

There are various core concepts of the bloc to understand its working,

Events

Events are the inputs of the bloc and it is generally done by making an interaction by the user

Interaction, while designing an app is the basic task and in the upcoming example, we will better know how to fire an event during an interaction.

Generally, we fire an event when we make any interaction when we call onTap but here in the example, we will be making an API call without any button interaction so the event will be fired when we will be instantiating it in main.dart

BlocProvider<UserDetailsBloc>(
builder: (context) => UserDetailsBloc()..add(FetchUserDetailsEvent()), child: UserPage()),

States

States are the output of the bloc, it notifies the UI component of the app and helps it to rebuild itself according to the state.

As we have defined event in the app and it is for fetching data from API, then it will be mapped into the state by bloc and UI will be redrawing itself according to it.

class UserDetailsLoadedState extends UserDetailsState {
UserResponseModel userResponseList;
UserDetailsLoadedState(this.userResponseList);
}

Transition

Transition helps to find out changes from one state to another, It holds the current state and the event and the next state

Basically, it helps to observe when and where states are getting changed It depends on the developer if he/she wants to use it or not.

Now after a small introduction let’s move to the core concept of Flutter Bloc

Bloc

Bloc (Business Logic Component) converts the incoming stream of events into outgoing stream of states

Every bloc converts every event into the state, and it can be accessed by using state property

@override
Stream<UserDetailsState> mapEventToState(UserDetailsEvent event) async* {
if (event is FetchUserDetailsEvent) {
yield UserDetailsLoading();
try {
UserModel userResponseList = await userRepository.userInfo(event.page);
yield UserDetailsSuccessState(userResponseList, event.page);
print(userResponseList);
} catch (e) {
print("error msg ${e.toString()}");
yield UserDetailsFailed(e);
}
}
}

Bloc Builder

Bloc Builder is a flutter widget that requires bloc and builder functions and it manages the widget according to new states

BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
// return widget here based on BlocA's state
}
)

BlocBuilder also contains few properties like bloc and condition for a specific purpose like when you want to specify a particular bloc and previous bloc. You can find a brief description here.

BlocProvider:

It is used as a dependency injection so that it can provide bloc to its children, there are different types to implement it but it depends on no. of blocs are implemented in any app, for single bloc you use BlocProvider.of<T>(context) but if you are managing your app by multiple blocs the MultiBlocProvider will be used to implement it

MultiBlocProvider(
providers: [
BlocProvider<UserDetailsBloc>(
builder: (context) => UserDetailsBloc()..add(FetchUserDetailsEvent()), child: UserPage()),
],
)

Now let’s implement it, for the sake of simplicity first create a package in the lib and make different classes for the event, state and bloc,

after this first, we make an event in the event class that will be fired when the application will start for fetching data.

abstract class UserDetailsEvent {
const UserDetailsEvent();
}

class FetchUserDetailsEvent extends UserDetailsEvent {
FetchUserDetailsEvent();
}

then we make all possible states which could happen in state class, state class must contain initial state because it is the state before any event has been received.

class UserDetailsState {}

class UserDetailsInitialState extends UserDetailsState {}

class UserDetailsLoading extends UserDetailsState {}

class UserDetailsLoadedState extends UserDetailsState {
UserResponseModel userResponseList;
UserDetailsLoadedState(this.userResponseList);
}

class UserDetailsError extends UserDetailsState {
Error e;
UserDetailsError(this.e);
}

there could be four possible states like

initial state,

loading state: when data is loading,

loaded state: when data is loaded,

error state: if we found any error.

Now let’s move to the bloc, here bloc mapEventToState means which event is fired will be converted in to map, in this code snippet we have called that event which we have fired from event class and all four possible states are used.


class UserDetailsBloc extends Bloc<UserDetailsEvent, UserDetailsState> {
UserRepository userRepository = UserRepository();

@override
UserDetailsState get initialState {
return UserDetailsInitialState();
}

@override
Stream<UserDetailsState> mapEventToState(UserDetailsEvent event) async* {
if (event is FetchUserDetailsEvent) {
yield UserDetailsLoading();
try {
UserModel userResponseList = await userRepository.userInfo(event.page);
yield UserDetailsSuccessState(userResponseList, event.page);
print(userResponseList);
} catch (e) {
print("error msg ${e.toString()}");
yield UserDetailsFailed(e);
}
}
}
}

now let’s understand how we call API using retrofit, for this, we have to add few dependencies retrofit, json_serializable, build_runner, and retrofit_generator

then we define and generate API and for this, you should have at least JSON parameters and then you to define it as mentioned below

here one thing is very important which is @JsonKey(name: “ ”) becuase if JSON response is containing key like “per_page”

then during the parsing, it would give an error, so you need to define it by @JsonKey(name: “per_page”) above the particular key.

then we write the factory method as it is, now it may contain few errors but leave it for now, we will handle it a few steps later

Now we make repository class

import 'dart:async';

import 'package:dio/dio.dart';
import 'package:flutter_demo_bloc_example/retrofit_client/user_details_client.dart';

class UserRepository {
static final UserRepository _singletonUserRepository =
UserRepository._internal();

factory UserRepository() {
return _singletonUserRepository;
}

UserRepository._internal();

Future<dynamic> userInfo(int page) async {
return await UserDetailsClient(Dio()).getUserDetail(page);
}
}

we implement HTTP methods and add parameters required

after this, we import

part 'class_name.g.dart';

in JSON model class(you can see above model class) run a command in the terminal,

flutter pub run build_runner build

here you will observe those errors have gone because all the required methods have been generated automatically and if you make any changes in it then you have to run the next command in the terminal,

flutter pub run build_runner build --delete-conflicting-outputs

the auto-generated files will be refactored.

after we call this API in bloc class and we fetch all the details using state in Our UI

https://gist.github.com/shivanchalpandey/58a32e41fb680e8a5cfb41fdd1f08ee0#file-user_page

this was a small introduction of flutter_bloc.


Thanks for reading this article

If you find something wrong please let me know I will love to improve.


FlutterDevs team of Flutter developers to build high-quality and functionally-rich apps. Hire flutter developer for your cross-platform Flutter mobile app project on an hourly or full-time basis as per your requirement! You can connect with us on Facebook, GitHub, and Twitter for any flutter related queries.

Leave comment

Your email address will not be published. Required fields are marked with *.