Introduction
Flutter, Google’s UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase, provides developers with a robust framework for creating beautiful and responsive user interfaces. However, as applications grow in complexity, managing the state becomes a crucial aspect of development. This is where Flutter Bloc and Freezed come into play, offering a powerful combination to streamline state management.
The Challenge of State Management
In any Flutter application, managing state effectively is vital for creating a smooth and responsive user experience. As the complexity of your app increases, handling different states, transitions, and side effects can become challenging. This is where state management solutions like Bloc come in handy.
Understanding Flutter Bloc
Bloc, short for Business Logic Component, is a state management library that helps in organizing and managing the flow of data within a Flutter application. It follows a unidirectional data flow architecture, making it easier to understand and maintain.
Bloc introduces the concept of events, states, and a bloc class. Events represent user actions or interactions, states represent the different states of your application, and the bloc class contains the business logic to handle events and emit new states.
Streamlining with Freezed
Freezed is a code generation package for Dart that helps eliminate boilerplate code when working with immutable classes and unions. When used in conjunction with Flutter Bloc, Freezed reduces the amount of repetitive code, making your application more maintainable and less error-prone.
One of the main features of Freezed is its ability to generate value equality for classes, making it easier to compare instances of these classes without manually implementing the == operator. This is especially useful when working with state classes in Flutter Bloc.
Getting Started
To get started with Flutter Bloc and Freezed, you’ll need to add the necessary dependencies to your pubspec.yaml
file:
dependencies:
flutter_bloc:
freezed_annotation:
dev_dependencies:
build_runner:
freezed:
Once the dependencies are added, you can start defining your state classes using Freezed and integrate them with Flutter Bloc.
Setting up the Bloc
Consider a simple counter example. First, create a folder named counter
in the root of your project.
Defining State with Freezed:
Next, create a State class to handle the application state. Within the counter
directory define a file named counter_state.dart
:
// counter/counter_state.dart
part of 'counter_bloc.dart';
@freezed
class CounterState with _$CounterState {
const factory CounterState({
required int counter,
}) = _CounterState;
factory CounterState.initial() {
return const CounterState(
counter: 0,
);
}
}
Defining Event with Freezed:
Next, create a Event class to handle user interactions. Within the counter
directory define another file named counter_event.dart
:
// counter/counter_event.dart
part of 'counter_bloc.dart';
@freezed
class CounterEvent with _$CounterEvent {
const factory CounterEvent.incrementButtonPressed() = _IncrementButtonPressed;
const factory CounterEvent.decrementButtonPressed() = _DecrementButtonPressed;
}
Creating the CounterBloc:
Next, create a Bloc class to handle the business logic. Within the counter
directory define another file named counter_bloc.dart
:
// counter/counter_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'counter_event.dart';
part 'counter_state.dart';
part 'counter_bloc.freezed.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState.initial()) {
on<CounterEvent>((event, emit) {
event.map(
incrementButtonPressed: (_) => emit(state.copyWith(
counter: state.counter + 1,
)),
decrementButtonPressed: (_) => emit(state.copyWith(
counter: state.counter - 1,
)),
);
});
}
}
Here, Freezed generates the necessary boilerplate code for equality and constructors.
Running the Code Generator:
To generate the code necessary for Freezed, run the following command in your terminal:
flutter pub run build_runner build
This command generates the counter_bloc.freezed.dart
file.
Using BlocBuilder in the UI
Now, you can use the BlocProvider
and BlocBuilder
widgets to connect your Bloc with the UI:
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter/counter_bloc.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: BlocProvider(
create: (context) => CounterBloc(),
child: const HomeScreen(),
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Counter App'),
),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'Counter value: ${state.counter}',
style: Theme.of(context).textTheme.headlineSmall,
);
},
),
),
floatingActionButton: Row(
mainAxisSize: MainAxisSize.min,
children: [
FloatingActionButton(
onPressed: () => context
.read<CounterBloc>()
.add(const CounterEvent.incrementButtonPressed()),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
const SizedBox(width: 16),
FloatingActionButton(
onPressed: () => context
.read<CounterBloc>()
.add(const CounterEvent.decrementButtonPressed()),
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
],
),
);
}
}
The BlocBuilder
automatically rebuilds the widget subtree whenever the state of the CounterBloc
changes, ensuring your UI stays in sync with the application state.
Conclusion
Combining Flutter Bloc and Freezed provides a powerful solution for state management in Flutter applications. By leveraging the unidirectional data flow of Bloc and eliminating boilerplate code with Freezed, developers can create scalable and maintainable applications with ease. Whether you’re building a simple counter app or a complex business application, Flutter Bloc and Freezed offer a solid foundation for efficient state management.
Top comments (5)
Very informative article! I'm just getting started with BLoC, is there a major advantage or disadvantage over the equatable approach?
Greetings, Boris
im pretty sure youre now know much about bloc since 4 February, so how do you think it is? I'm new in flutter and bloc state management, can you tell me?
Not too much. Eventually I went with Equatable, because I wanted to introduce my team into Flutter and since no one has any experience I wanted to keep it a bit more simple. Eventually I will want to look into Freezed and see if the extra build step is worth the setup. We have little BLoCs and few state variables after all. So the repeating code is a non-issue (yet 🙈).
Freezed is good to go tho, but its little confusing when using it with BLoC, but after looking some explain, its truly a genius package!
Yeah I can't wait looking into it after the project is finished. I got tired of implementing
copyWith()
for all my states 🥲