Flutter bloc- State management and api integration
Learn how to manage the state of application and api integration using flutter bloc.
Managing the state of the application is an integral part of the application. As we come to flutter, it provides various ways to manage the application state using packages such as Provider, GetX, MobX, Bloc, Riverpod, flutter bloc, etc.
In this article, we will explore managing the state of the application using the flutter bloc package.
Flutter bloc
Flutter bloc is fast and powerful. It is easy to learn and supported by the large community. It helps us to separate the business logic from the view and manage events and the state of the application. By using flutter bloc we can create events and the state of the application easily. Also, the code looks very clean as we create separate blocs of the application according to the feature used in the application.
Also flutter bloc has good documentation, in which it has detailed information about the bloc library. It has lots of demo applications to learn and practice, so the concepts of flutter bloc get cleared.
The flow of the flutter bloc
It is necessary to understand how does flutter bloc works. Whenever any button or action is done on the screen, it will trigger the event to the bloc and the bloc will respond to the event and emit the data due to which the state is declared.
For example:
Let's consider we have to fetch the data from the api and display the data on the home page.
Here from the home page, an event will be triggered to the bloc.
In the bloc, api call will be done and based on the response of the api the data will be emitted and the state is defined.
Here we consider the state as success or error.
Based on the state of the application the data or error is displayed on the home page as it is continuously listening to the state.
We will work on the demo application, which will fetch some random comments from api and display them on the screen. From the comments list, we will add a feature to make it a favorite and these favorites are displayed on the other screen
We need to install the following dependencies. Go to pub.dev, check for the latest version, and install them in pubspec.yaml file.
flutter_bloc → We will use this package to maintain the state of the application and it also helps us to integrate bloc easily.
http → We are using this package to make http request which will help us to fetch the data from given url.
equatable → This package which helps to compare the object without needing to explicitly override == and hashcode.
Folder structure
We will create a separate folder to maintain each page separately according to the requirement.
api_provider → Here we create a file which handles the api request.
bloc → In this directory we will create a files which handles the events, state and bloc structure of the application.
constants → Here we create a file whihc handles the constants used in the file.
models → Here we will create a model file for the response of api which will helps us to parse the json to model data.
pages → Here we will create all the page file required.
In the main file, wrap the MaterialApp with the MultiBlocProvider. Here we can initialize all the blocs we have created to manage the state of the application.
MultiBlocProvider(
providers: [
BlocProvider<CommentsBloc>(
create: (context) => CommentsBloc(CommentsApiProvider())),
BlocProvider<FavoritesBloc>(create: (context) => FavoritesBloc())
],
child: MaterialApp(
title: 'Flutter Bloc',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.orange,
appBarTheme: const AppBarTheme(
color: Colors.orange,
centerTitle: true,
titleTextStyle: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
))),
home: const CommentsPage(),
))
Create a dart file in the constants directory and name it constants.dart. In this file create a class with the class name Constants.
In this file, we are defining the URL through which we will fetch the list of comments. We can easily get this URL by using Constants.endUrl.
constanst.dart file
class Constants {
static const endUrl = "https://jsonplaceholder.typicode.com/comments";
}
Create a dart file in the model’s folder and name it comment_model.dart file.
Now we need to create the model of the given comments url. For this, we can use the quicktype.io tool. It will help us to create the models fast and easily. Just paste the response of the comments url and it will generate the model for the given data.
Copy the model data from quicktype.io and paste it into comment_model.dart file.
comments_models.dart
import 'dart:convert';
List<Comments> commentsFromJson(String str) =>
List<Comments>.from(json.decode(str).map((x) => Comments.fromJson(x)));
String commentsToJson(List<Comments> data) =>
json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class Comments {
Comments({
required this.postId,
required this.it,
required this.name,
required this.email,
required this.body,
});
int? postId;
int? it;
String? name;
String? email;
String? body;
factory Comments.fromJson(Map<String, dynamic> json) => Comments(
postId: json["postId"],
it: json["it"],
name: json["name"],
email: json["email"],
body: json["body"],
);
Map<String, dynamic> toJson() => {
"postId": postId,
"it": it,
"name": name,
"email": email,
"body": body,
};
}
Now we need to create a file which will handle the api request to fetch the data using http.
Create a dart file in api provider directory and name it comments_api_provider.dart. In this, we handle the api request to get the comments from the given url. Here we also parse the api data to model as the response is a success.
/// Handles the api request for comments
class CommentsApiProvider {
// Gets the list of comments from the api
Future<List<Comments>> getComments() async {
Response response = await get(Uri.parse(Constants.endUrl));
// Parse the response to the comments model if
// status code is 200 else throw exception
if (response.statusCode == 200) {
return commentsFromJson(response.body);
} else {
throw Exception(response.reasonPhrase);
}
}
}
Now we need to handle the state, event and bloc for the comments.
For this, we need to create three files in the bloc directory.
Create a dart file and name it comments_bloc.dart. In this file, we will handle the business logic to call the api when requested by any event and emits the api data. This file also helps us to set the state for the comments.
/// Comments bloc calls to handle events and set state for the comments
class CommentsBloc extends Bloc<CommentsEvent, CommentsState> {
// Here we create the instance of comments api provider so we can
// api methods easily
final CommentsApiProvider _commentsRepo;
CommentsBloc(this._commentsRepo) : super(CommentsLoadingState()) {
// Here it will respond the event and emit the state for comments
on<LoadCommentsEvent>((event, emit) async {
// Sets the comments state to loading
emit(CommentsLoadingState());
try {
// Here we will call api and emits the comments data
final comments = await _commentsRepo.getComments();
// Sets the state to loaded and passes the comments received from api
emit(CommentsLoadedState(comments));
} catch (e) {
emit(CommentsErrorState(e.toString()));
}
});
}
}
Create a new dart file in bloc directory and name it as comments_event.dart.
In this file, we will handle events to trigger the bloc. As we are displaying the list of comments, we just need an event to observe the changes to the list.
Here we will create an abstract class that will extend to equatable. It will help us to compare the two objects easily.
/// Comments class extends to equatable, which helps to compare the object easily
/// without any boiler code
@immutable
abstract class CommentsEvent extends Equatable {
const CommentsEvent();
}
class LoadCommentsEvent extends CommentsEvent {
// List of properties that determines two instance are equal
@override
List<Object?> get props => [];
}
Now we need to defines the state of the comments.
Let's create a new dart file in the bloc directory and name it comments_state.dart.
Here we need to check whether the comments are loaded, loading, or if there is any error while fetching the list of comments from the api. Based on this we can create separate classes for each state.
/// Helps to compare two object easily
@immutable
abstract class CommentsState extends Equatable {}
// It will define the state loading
class CommentsLoadingState extends CommentsState {
@override
List<Object?> get props => [];
}
// It will define the state loaded
class CommentsLoadedState extends CommentsState {
final List<Comments> comments;
CommentsLoadedState(this.comments);
// Gets the comments data if the state is loaded
@override
List<Object?> get props => [comments];
}
// It will define the state error
class CommentsErrorState extends CommentsState {
final String error;
CommentsErrorState(this.error);
@override
List<Object?> get props => [error];
}
Now it's time to show the data on a screen. Create a dart file in the page folder and name it comments_page.dart.
In this file, we will display the number of items added to favorites and the icon button to navigate to the favorites page in the appbar.
We will display the number by using BlocProvider. It helps to provide a bloc to its child who works as a dependency injection.
For example, we can get the length of our favorite list by using the following code
BlocProvider.of<FavoritesBloc>(context)
.favoriteItems
.length
We also use BlocProvider and BlocBuilder to display the list of comments. BlocBuilder requires a builder function that builds the widget based on the state. Based on the state we can display the loader widget or error widget.
BlocBuilder<CommentsBloc, CommentsState>(builder: (context, state) {
if (state is CommentsLoadingState) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state is CommentsErrorState) {
return const Center(child: Text("Error..."));
}
}
class CommentsPage extends StatefulWidget {
const CommentsPage({super.key});
@override
State<CommentsPage> createState() => _CommentsPageState();
}
class _CommentsPageState extends State<CommentsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Comments"),
actions: [
// Displaying the number favorites available
// using bloc builder
if (context.watch<FavoritesBloc>().favoriteItems.isNotEmpty)
Text(
BlocProvider.of<FavoritesBloc>(context)
.favoriteItems
.length
.toString(),
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 20)),
// Displaying the favorites icon to navigate to favorites screen
if (context.watch<FavoritesBloc>().favoriteItems.isNotEmpty)
IconButton(
onPressed: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (_) => const FavoritesPage()));
},
icon: const Icon(
Icons.favorite,
color: Colors.white,
)),
],
),
body: _commentsBody(),
);
}
// A widget which displays the list of comments with favorite icon button
// to save it in favorite list
Widget _commentsBody() {
return BlocProvider(
create: (context) =>
CommentsBloc(CommentsApiProvider())..add(LoadCommentsEvent()),
child:
BlocBuilder<CommentsBloc, CommentsState>(builder: (context, state) {
// Displays the loader if comments state is loading
if (state is CommentsLoadingState) {
return const Center(
child: CircularProgressIndicator(),
);
}
// Displays the error if comments state is error
if (state is CommentsErrorState) {
return const Center(child: Text("Error..."));
}
// Displays the list of comments if comments state is loaded
if (state is CommentsLoadedState) {
List<Comments> comments = state.comments;
return ListView.builder(
itemCount: comments.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
padding: const EdgeInsets.all(10),
color: Colors.grey.shade300,
child: Row(
children: [
// Displays the comments
Container(
width: MediaQuery.of(context).size.width * 0.75,
child: Text(comments[index].body ?? "")),
// Displays the icon button
IconButton(
onPressed: () {
// Removes from the favorite list
if (BlocProvider.of<FavoritesBloc>(context)
.favoriteItems
.contains(comments[index].body)) {
context
.read<FavoritesBloc>()
.favoriteItems
.remove(comments[index].body.toString());
// BlocProvider.of<FavoritesBloc>(context)
// .favoriteItems
// .remove(comments[index].body.toString());
} else {
// BlocProvider.of<FavoritesBloc>(context)
// .favoriteItems
// .add(comments[index].body.toString());
// Adds to the favorite list
context
.read<FavoritesBloc>()
.favoriteItems
.add(comments[index].body.toString());
}
setState(() {});
},
icon: Icon(
Icons.favorite,
color: context
.read<FavoritesBloc>()
.favoriteItems
.contains(comments[index].body)
? Colors.red
: Colors.white,
))
],
)),
);
});
}
// Display empty container of no state id define
return Container(
color: Colors.red,
);
}),
);
}
}
Get the source code from the git. Learn how to manage the state and event for the favorites. Also, learn how to add/remove items from the list of favorites.
Let me know in the comments if I need to correct anything. I will try to improve it.
Clap if you like the article. 👏
Learn about state management using GetX from my article.