State Management in Flutter with Bloc and Freezed
Tags: flutter, freezed, flutterbloc, blocpattern
Published: 2023-12-08 03:11:19

## 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:

```yaml
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`:

```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`:

```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`:

```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:

```bash
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:

```dart
// 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.