> For the complete documentation index, see [llms.txt](https://wong-coupon.gitbook.io/flutter/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wong-coupon.gitbook.io/flutter/state-management/bloc.md).

# Bloc

## Why Use Bloc?

[Bloc](https://bloclibrary.dev/) is built on top of Flutter's [Provider](https://pub.dev/packages/provider) and has been marked as a **Favorite** package by Flutter. It simplifies the code required when using Provider and is currently one of the best state management solutions for Flutter.

Bloc may seem complex for beginners, but compared to simpler state management solutions like [GetX](https://pub.dev/packages/get), Bloc offers better screen rendering optimization and more convenient state management. Both Bloc and [Riverpod](https://riverpod.dev/) are based on Provider, so you can choose a state management tool based on your preference. Bloc also provides a simpler state management approach called [Cubit](https://bloclibrary.dev/bloc-concepts/), making it beneficial for both small-scale and large-scale projects.

For large-scale projects, Bloc is commonly used ([Bloc concepts](https://bloclibrary.dev/bloc-concepts/#bloc)), but it requires writing a significant amount of code. To address this, **Mason** can be used as a solution. I will explain how I automate my Bloc code templates in the next section.

In addition to state management, Bloc offers other useful features that I frequently use, such as:

* **Caching and persisting Bloc states**, which is useful for storing application settings.
* **Redo and Undo functionalities** for state transitions.

For more details, refer to [Bloc Packages](https://bloclibrary.dev/getting-started/#packages).

## Code Faster with Mason

You can check out my Bloc template at [dr\_bloc](https://brickhub.dev/bricks/dr_bloc) and create one for yourself. If you are not familiar with Mason, refer to this guide: [Boost Your Flutter Development Efficiency with Mason](https://mymoneycoupon.web.app/en/doc/help/flutter/boost-your-flutter-development-efficiency-with-mason).

<figure><img src="/files/PfMXNp826lQtuHOKlx8C" alt=""><figcaption><p>Code Faster with Mason</p></figcaption></figure>

My Bloc is designed as part of the **MVVM architecture**, where the UI communicates directly with the Bloc. The Bloc is responsible for retrieving data from repositories. You can explore the official Bloc architecture [here](https://bloclibrary.dev/architecture/). If you want to implement it within a **clean architecture**, check out this article: [Maintain and Extend Code Easier with Clean Architecture](https://mymoneycoupon.web.app/en/doc/help/flutter/maintain-and-extend-code-easier-with-clean-architecture).

Most of us get used to a specific coding style, but when switching to different projects, rewriting everything from scratch slows down development. For simple modules, **Cubit** is a great choice, but for **larger and more complex modules**, we need a more structured and explicit state management approach.

#### My Bloc Structure

I organize my Bloc into a single folder with two main parts:

1. **Bloc for handling state logic.**
2. **Event and state combined into a single file** (since they are not too long), reducing unnecessary files.
   * I also use [**json\_serializable**](https://pub.dev/packages/json_serializable) to facilitate **state persistence** using `hydrated_bloc` and `replay_bloc`.

This structure allows us to define **state enums** or default app settings at initialization.

```dart

/// Part I: State
// BlocSubject + State
// Learn more: https://bloclibrary.dev/naming-conventions/#single-class
@JsonSerializable(explicitToJson: true)
final class {{name.pascalCase()}}State extends Equatable {
    const {{name.pascalCase()}}State({
      this.user,
    })
    // : Setup Object: https://dart.dev/codelabs/dart-cheatsheet#initializer-lists
        ;
    
    // Default value
    static const {{name.pascalCase()}}State origin = {{name.pascalCase()}}State();
    
    // @JsonKey(
    //   includeFromJson: false,
    //   includeToJson: false
    // )
    final String? user;
    
    {{name.pascalCase()}}State copyWith({
        String? user,
        // User? Function()? user,
    }) {
    return {{name.pascalCase()}}State(
        user: user ?? this.user,
        // user: user != null ? user() : this.user,
        );
    }
    
    @override
    List<dynamic> get props => [
      user,
    ];
    
    factory {{name.pascalCase()}}State.fromJson(MyJsonMap json) => _${{name.pascalCase()}}StateFromJson(json);
    
    MyJsonMap toJson() => _${{name.pascalCase()}}StateToJson(this);
    
    @override
    String toString() {
      return '''{{name.pascalCase()}}State  ${toJson().myPrintMapJson}''';
    }
}

```

This Section Will Contain Bloc Events

```dart
/// Part II: Event
// Learn more: https://bloclibrary.dev/naming-conventions/#anatomy
// BlocSubject + Noun (optional) + Verb (event)
sealed class {{name.pascalCase()}}Event
// extends ReplayEvent
{
  const {{name.pascalCase()}}Event();
}
final class {{name.pascalCase()}}Pressed extends {{name.pascalCase()}}Event {
    const {{name.pascalCase()}}Pressed();
    // final String something;
    // const {{name.pascalCase()}}Pressed({required this.something});
}
```

This section will contain the Bloc, where you can customize your functions.

```dart
/// Part III: Bloc
// Learn more bloc: https://bloclibrary.dev/bloc-concepts/#bloc
class {{name.pascalCase()}}Bloc extends Bloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> {
    {{name.pascalCase()}}Bloc() : super({{name.pascalCase()}}State.origin) {
      on<{{name.pascalCase()}}Pressed>(on{{name.pascalCase()}}Pressed);
    }
    
    Future<void> on{{name.pascalCase()}}Pressed({{name.pascalCase()}}Pressed event, Emitter<void> emit) async {
      emit(state);
    }
}
```

In the Bloc file, I have pre-written code for Cubit. You can uncomment it to use, or remove it if you find it unnecessary.

```dart
class {{name.pascalCase()}}Cubit extends Cubit<{{name.pascalCase()}}State> {
    {{name.pascalCase()}}Cubit() : super({{name.pascalCase()}}State.origin);
    
    void on{{name.pascalCase()}}Pressed() {
      emit(state);
    }
    
    @override
    void onChange(Change<{{name.pascalCase()}}State> change) {
      super.onChange(change);
      myLog.trace(change);
    }
    
    @override
    void onError(Object error, StackTrace stackTrace) {
      myLog.trace('$error, $stackTrace');
      super.onError(error, stackTrace);
    }
}
```

To make the Bloc work, we need to add it to the **BlocProvider**. Below is an example of declaring a Bloc before using it:

```dart
 MultiBlocProvider(
      providers: [
        BlocProvider (
          create: (BuildContext context) => ABloc(),
        ),
        BlocProvider (
          create: (BuildContext context) => BBloc(),
        ),
        ...
      ],
      child: const AppView(),
    );
```

Make sure to explore **Bloc extensions** for IDEs like **Android Studio** to speed up your development process. You can also create **live templates** to reuse frequently used code snippets efficiently.

## Debugging and Logging Bloc Events

This feature is already covered in the **Bloc documentation**. Typically, it's only necessary for critical Blocs where you need to **monitor event flow** or **debug errors**.

```dart
/// Logging state area
  @override
  void onChange(Change<{{name.pascalCase()}}State> change) {
    super.onChange(change);
    // myLog.trace(change);
  }

  // capture information about what triggered the state change
  // Learn more: https://bloclibrary.dev/bloc-concepts/#:~:text=One%20key%20differentiating,overriding%20onTransition.
  @override
  void onTransition(Transition<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> transition) {
    super.onTransition(transition);
    myLog.trace(transition);
  }

  @override
  void onEvent({{name.pascalCase()}}Event event) {
    super.onEvent(event);
    // myLog.trace(event);
  }

  @override
  void onError(Object error, StackTrace stackTrace) {
    myLog.trace('$error, $stackTrace');
    super.onError(error, stackTrace);
  }
  
```

Another way to always listen to these changes without writing them into each individual Bloc is available.

```dart
/// {@template app_bloc_observer}
/// Custom [BlocObserver] that observes all bloc and cubit state changes.
/// {@endtemplate}
class OurBlocObserver extends BlocObserver {
/// {@macro app_bloc_observer}
    const OurBlocObserver();
    @override
    void onChange(BlocBase<dynamic> bloc, Change<dynamic> change) {
        super.onChange(bloc, change);
        if (bloc is Cubit) {
          myLog.trace(change.toString().myPrintStringJson, flag: "Blog");
        }
    }

    @override
    void onTransition(
    Bloc<dynamic, dynamic> bloc,
    Transition<dynamic, dynamic> transition,
    ) {
      super.onTransition(bloc, transition);
      // myLog.trace((transition.toString().myToStringJson() ?? "").myPrintStringJson, flag: "Blog");
    }
    
    @override
    void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
      myLog.warning('${bloc.runtimeType} $error $stackTrace');
      super.onError(bloc, error, stackTrace);
    }
}
```

Call it after the application has fully initialized, as shown below:

```dart
WidgetsFlutterBinding.ensureInitialized();

Bloc.observer = const OurBlocObserver();
```

## Persisting Bloc State to Device Storage

To save the Bloc state to device storage for the next app launch, refer to:\
🔗 [Replay Bloc Documentation](https://pub.dev/packages/replay_bloc#replayblocmixin)

#### **Step 1: Replace Event Definition**

Change:

```dart
{{name.pascalCase()}}Event
```

To:

```dart
sealed class {{name.pascalCase()}}Event extends ReplayEvent {}
```

#### **Step 2: Replace Bloc Definition**

Change:

```dart
{{name.pascalCase()}}Bloc
```

To:

```dart
class {{name.pascalCase()}}Bloc extends HydratedBloc<{{name.pascalCase()}}Event, {{name.pascalCase()}}State> with ReplayBlocMixin {
```

Then, import these two libraries:

```dart
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:replay_bloc/replay_bloc.dart';
```

#### **Step 3: Add JSON Serialization Functions**

Inside `{{name.pascalCase()}}Bloc`, implement the following:

```dart
@override
{{name.pascalCase()}}State? fromJson(MyJsonMap json) {
  return {{name.pascalCase()}}State.fromJson(json);
}

@override
MyJsonMap? toJson({{name.pascalCase()}}State state) {
  return state.toJson();
}
```

#### **Step 4: Initialize HydratedBloc After App Launch**

Make sure to initialize `HydratedBloc` after the app has fully loaded.\
If you're not using `MyFlutter`, replace it with the appropriate storage directory for your project.\
More details: [Hydrated Bloc Usage](https://pub.dev/packages/hydrated_bloc#usage)

```dart
WidgetsFlutterBinding.ensureInitialized();

HydratedBloc.storage = await HydratedStorage.build(
  storageDirectory: kIsWeb
      ? HydratedStorage.webStorageDirectory
      : await MyFlutter.file.appDocumentsDir,
);
```

## **Calling Bloc from Anywhere in the Project**

Here is an example of how I can access `context` from anywhere:

```dart
final isUsageTimeLimit = myNavigatorKey.currentContext!.read<SettingBloc>().state.setting.otherSetting.isUsageTimeLimit;
```

Check out my article on accessing `context` anywhere here to learn how to set it up.

{% content-ref url="/pages/MnpADg71Yuu0vcuuCWTo" %}
[Context Anywhere](/flutter/state-management/context-anywhere.md)
{% endcontent-ref %}

This is how I use **Bloc** in my projects. You can adapt it to create your own **code template** that fits your projects, helping **reduce development time** while still leveraging the full power of Bloc.\
\
[Buy Me a Coffee](https://buymeacoffee.com/ducmng12g) | [Support Me on Ko-fi](https://ko-fi.com/I2I81AEJG8)
