State Management in Flutter: Using Cubit + Freezed with BLoC Pattern
State management is the backbone of any Flutter application, ensuring smooth interactions and responsive interfaces. This guide will dive deep into Cubit + Freezed with Bloc—a powerhouse combination designed to streamline state management and enhance your Flutter projects.
Why Cubit + Freezed with BLoC?
Streamlined State Management
Managing state in Flutter can be complex, especially as your app grows. Cubit + Freezed with Bloc provides a structured approach that separates business logic from UI, making your codebase cleaner and more maintainable. With Cubit, you define discrete logic units for handling state changes, while Freezed helps you generate immutable state classes effortlessly. Bloc ties it all together, ensuring your UI reacts to state changes efficiently.
Enhanced Code Structure
Adopting Cubit + Freezed with Bloc creates a clear architectural pattern that promotes scalability and code reusability. This setup encourages a single source of truth for your app's state, reducing bugs and improving code predictability. Whether you're building a simple counter app or a complex eCommerce platform, this approach scales with your project's needs.
What You'll Gain
Efficient State Updates
With Cubit, state updates are handled efficiently using the emit
method, ensuring only necessary parts of your UI are updated when state changes occur. This optimizes performance and provides a seamless user experience.
Code Clarity and Predictability
Freezed generates immutable state classes based on your models, reducing boilerplate code and eliminating runtime errors related to mutable states. This guarantees that your app remains stable and predictable throughout its lifecycle.
Implementing Cubit + Freezed with Bloc
Step-by-Step Setup
Let's set up your project to harness the power of Cubit + Freezed with Bloc.
1. Project Structure
Organize your project with a clear separation of concerns. Here's the recommended structure:
lib/
|-- di/
| |-- injection.dart
|
|-- home/
| |-- home_screen.dart
| |-- cubit/
| | |-- home_cubit.dart
| | |-- home_state.dart
|
|-- main.dart
2. Dependency Injection
Configure dependencies using get_it
and injectable
in injection.dart
:
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'injection.config.dart';
final getIt = GetIt.instance;
@InjectableInit(
initializerName: 'init',
preferRelativeImports: true,
asExtension: true,
)
void configureDependencies() => getIt.init();
3. State Management with Freezed
Define your app's state using Freezed annotations in home_state.dart
:
import 'package:freezed_annotation/freezed_annotation.dart';
part 'home_state.freezed.dart';
@freezed
class HomeState with _$HomeState {
const factory HomeState.initial({
@Default(0) int counter,
}) = _Initial;
}
4. Creating Cubit
Implement your Cubit for state management in home_cubit.dart
:
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:injectable/injectable.dart';
part 'home_state.dart';
part 'home_cubit.freezed.dart';
@injectable
class HomeCubit extends Cubit<HomeState> {
HomeCubit() : super(const HomeState.initial());
void incrementCounter() {
emit(state.copyWith(counter: state.counter + 1));
}
}
5. Integrating with UI
Integrate your Cubit with BlocProvider
and BlocBuilder
in home_screen.dart
:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../di/injection.dart';
import 'cubit/home_cubit.dart';
class HomeScreen extends StatelessWidget {
final HomeCubit cubit = getIt<HomeCubit>();
HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => cubit,
child: Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
BlocBuilder<HomeCubit, HomeState>(
builder: (context, state) {
return Text(
'${state.counter}',
style: Theme.of(context).textTheme.headline4,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
cubit.incrementCounter();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
),
);
}
}
6. Using Build Runner
To ensure optimal performance and maintain a streamlined development process, we utilize Build Runner—a code generation tool in Dart and Flutter. Build Runner automates the generation of boilerplate code, reducing manual effort and enhancing productivity.
Generating Code
Before diving into the details, it's crucial to generate the necessary code using Build Runner. Open your terminal or command prompt, navigate to your project directory, and execute the following command:
dart run build_runner build
This command triggers Build Runner to generate code based on annotations and configurations in your project. It's a one-time execution unless your project's structure or dependencies change significantly.
Continuous Code Generation with Watch
During development, you'll often make incremental changes to your project. To keep your generated code up-to-date automatically, utilize the watch mode of Build Runner. Simply run:
dart run build_runner watch
With this command, Build Runner monitors your project files for changes. Upon detecting modifications, it automatically regenerates the necessary code, ensuring your app remains in sync with the latest logic and configurations.
Note: Until you run dart run build_runner build
, you may encounter errors related to the code that is expected to be generated. This is normal and resolves once the code generation is complete.
Conclusion
By integrating Build Runner into your Flutter development workflow, you streamline code generation and maintain optimal performance. Embrace the efficiency of Build Runner—whether generating code once with build
or continuously updating with watch
—and elevate your Flutter projects to new heights of efficiency and scalability.
GitHub Repository
For a complete implementation example, check out the GitHub repository here.
Subscribe to my newsletter
Read articles from Shankar Kakumani directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by