Flutter State Management: Bloc vs. setState — A Comparative Analysis
In this article, we will explore the appropriate use cases for setState
and BLoC
(Business Logic Component) in Flutter state management. We'll examine when to use each approach and the potential challenges they address.
It’s not a good idea to get caught taking ice cream from the fridge by shouting when your parents are sleeping, just as it’s not efficient to rebuild all the widgets underneath a specific one when you can use a state management pattern like BLoC.
In the first diagram, which represents a widget tree, we can see that using setState
at a particular widget, such as the "Row" widget, causes all of its ancestor widgets to be rebuilt. This can be inefficient, especially when dealing with nested containers or complex widget trees. Unnecessary rebuilding can lead to performance issues.
Now, let’s delve into how BLoC works:
With BLoC, you can manage the state of your application in a more efficient and organized manner. Let’s say you have a variable that is only used within the “Row” widget. When you use BLoC, you can update the state of this specific variable without triggering the rebuilding of all the widgets above it in the hierarchy.
This approach becomes particularly valuable in larger enterprise-level applications. In such cases, performance optimization is crucial, and using BLoC or a similar state management solution becomes essential. By doing so, you ensure that your app remains responsive and efficient, even as its complexity grows.
let’s discuss how BLoC works using an example of incrementing a variable. In BLoC-based implementations, you typically have three essential components:
- Main BLoC: This is where changes to your variable occur. It acts as a central hub for managing state and logic.
- BLoC State: States represent different conditions or values of your variable. Every event should correspond to a state. States are used to reflect changes in your variable and its associated data.
- BLoC Events: Events are actions or triggers that lead to changes in your variable’s state. Events are typically associated with user interactions or other external factors that affect your variable.
To increment a variable, we can create an event called IncrementEvent
, which signifies the intention to increase the variable's value. Additionally, we need to define a state called UpdateState
to represent the new value of the variable after it's been incremented. Here's how we can structure our
//main bloc
import 'package:bloc_project/variable_event.dart';
import 'package:bloc_project/variable_state.dart';
import 'package:bloc/bloc.dart';
class CounterBloc extends Bloc<IncrementEvent,UpdateState > {
CounterBloc() : super(const UpdateState()) {
on<VariableUpdateEvent>((event, emit) {
emit(VariableUpdateState(x :event.x!+1));
});
}
}
//event class
class IncrementEvent {
const IncrementEvent();
}
class VariableUpdateEvent extends IncrementEvent {
final int? x;
const VariableUpdateEvent(this.x);
}
//state class
class UpdateState {
const UpdateState();
}
class VariableUpdateState extends UpdateState {
final int? x;
const VariableUpdateState({this.x});
}
//UI
class MyHomePage extends StatelessWidget {
MyHomePage({super.key, required this.title});
final String title;
int _counter = 0;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => CounterBloc(),
child: variableMainBloc(),
);
}
variableMainBloc() {
return BlocListener<CounterBloc, UpdateState>(
listener: (context, state) {},
child: BlocBuilder<CounterBloc, UpdateState>(
builder: (context, state) {
if (state is VariableUpdateState) {
_counter = state.x!;
return bodyLoader(context);
}
return bodyLoader(context);
},
),
);
}
Widget bodyLoader(context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(title),
),
body: Center(
child: Column(
children: [
Container(
color: Colors.green,
padding:const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: InkWell(
onTap: () {
BlocProvider.of<CounterBloc>(context)
.add(VariableUpdateEvent(_counter));
},
child: Text("click value is +$_counter"),
),
),
],
),
));
}
}
In this example:
IncrementEvent
represents the intention to increment the variable.UpdateState
represents the new value of the variable after it's been incremented.- The
CounterBloc
handles theIncrementEvent
and increments the variable. It then emits anUpdateState
with the updated value.
Now, when you dispatch an IncrementEvent
to the CounterBloc
, it will increment the variable and emit an UpdateState
, which can be observed by the UI to reflect the updated value.
In the above code we came across components like bloc create, bloc listener,bloc builder.hese components help you manage the state and behavior of your application.
Bloc Create (Bloc Initialization) :
- This component refers to the initialization of your BLoC instances. In your application, you create instances of your BLoC classes to manage different parts of the application’s state and logic.
- Typically, you create BLoC instances at the top level of your widget hierarchy, often within the
main
function or thebuild
method of your app's root widget.
BlocBuilder :
BlocBuilder
is a widget provided by theflutter_bloc
package that rebuilds its child widget whenever the state of a specifiedBloc
changes.- It takes a
Bloc
and a builder function. The builder function receives the currentBloc
state and returns a widget that depends on that state. - It’s used to efficiently update the UI in response to changes in the
Bloc
's state.
BlocListener :
BlocListener
is another widget provided by theflutter_bloc
package that listens to state changes in aBloc
but doesn't rebuild its child widget.- It’s useful for responding to state changes by executing side effects or performing actions in your UI.
- It takes a
Bloc
and a listener function. The listener function is called whenever theBloc
's state changes.
Bloc Provider:
- Bloc Provider is a widget that facilitates the creation and management of BLoCs.
- It ensures that the same BLoC instance is provided consistently throughout the widget tree.
- Bloc Provider is responsible for initializing BLoCs and making them accessible to parts of your application that need them.
- This component helps centralize BLoC management, prevent memory leaks, and maintain a clean separation of concerns.
This is a basic example of how BLoC can be used to manage state and logic for incrementing a variable. BLoC helps maintain a clear separation between the UI and business logic, making it easier to manage complex state and events in your Flutter application.
Github repository
The code is open source, so if you want to get it you can do it here: https://github.com/shivrajbande/bloc_example.
Thanks for reading this article, I hope that now you could understand better how flutter bloc works and If this was helpful for you I’ll really appreciate your feedback and also your claps!👏