DEV Community

Cover image for State Management in Flutter with Bloc and Freezed
Vishnu C Prasad
Vishnu C Prasad

Posted on • Originally published at vishnucprasad.Medium

State Management in Flutter with Bloc and Freezed

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:
Enter fullscreen mode Exit fullscreen mode

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,
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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,
        )),
      );
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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),
          ),
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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 (1)

Collapse
 
schmoris profile image
Boris

Very informative article! I'm just getting started with BLoC, is there a major advantage or disadvantage over the equatable approach?
Greetings, Boris