flutter
/

BLoC Code Generation: Automate Boilerplate with bloc_generator & Annotations

Last Sync: Today

On this page

11
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

BLoC Code Generation: Automate Boilerplate with bloc_generator & Annotations

What is BLoC Code Generation?

BLoC code generation refers to using tools like bloc_generator and bloc_annotations to automatically create the boilerplate code for your BLoCs, states, and events. Instead of manually writing repetitive classes for each feature, you can use annotations to define the structure, and the build system will generate the necessary files. This approach reduces human error, enforces consistency, and speeds up development.

Why Use Code Generation?

  • Reduces Boilerplate – No more manually writing states, events, and bloc classes.
  • Consistency – All blocs follow the same pattern, making the codebase easier to navigate.
  • Time Saving – Focus on business logic rather than repetitive coding.
  • Type Safety – Generated code is fully typed and integrates seamlessly with flutter_bloc.
  • Less Human Error – Automatically generated equality, copyWith, and event handlers.

Setting Up Dependencies

Add the required packages to your pubspec.yaml. You'll need bloc_annotations and bloc_generator for code generation, plus build_runner to run the generator.

YAMLRead-only
1
dependencies:
  flutter_bloc: ^8.1.5
  bloc_annotations: ^1.0.0

dev_dependencies:
  build_runner: ^2.4.0
  bloc_generator: ^1.0.0

Basic Usage: Annotating a BLoC

Create a file (e.g., counter_bloc.dart) and use the @Bloc annotation to define the state and events. The generator will produce the full BLoC class, states, and events.

DARTRead-only
1
import 'package:bloc_annotations/bloc_annotations.dart';

part 'counter_bloc.g.dart';

@Bloc()
abstract class CounterBloc {
  @initialState()
  int get initialState => 0;

  @event()
  void increment(int currentState) => currentState + 1;

  @event()
  void decrement(int currentState) => currentState - 1;
}

Run the generator:

BASHRead-only
1
dart run build_runner build
# or watch for changes
flutter pub run build_runner watch

After generation, you'll have counter_bloc.g.dart containing the CounterBloc class, CounterEvent classes, and CounterState (which extends Equatable). You can then use the generated bloc normally.

Generated Code Structure

The generator typically creates:

  • A sealed union of events – One class per event method, with fields if parameters are passed.
  • A state class – Extends Equatable and contains the state value (or multiple fields).
  • The BLoC class – Extends Bloc<GeneratedEvent, GeneratedState> with event handlers wired up.
DARTRead-only
1
// Generated code (simplified)
class CounterState extends Equatable {
  final int value;
  const CounterState(this.value);
  @override List<Object?> get props => [value];
}

abstract class CounterEvent extends Equatable {}
class IncrementEvent extends CounterEvent {}
class DecrementEvent extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) => emit(CounterState(state.value + 1)));
    on<DecrementEvent>((event, emit) => emit(CounterState(state.value - 1)));
  }
}

Advanced Annotations

You can customize the generated code with additional annotations.

DARTRead-only
1
@Bloc()
abstract class TodoBloc {
  @initialState()
  TodoState get initialState => TodoState(todos: []);

  @event()
  Future<void> addTodo(Todo todo, TodoState state) async {
    // logic here
  }

  @event(
    transformer: 'debounce(Duration(milliseconds: 300))',
  )
  void search(String query, TodoState state) {
    // ...
  }
}

Custom State Classes

If you need a custom state with multiple fields, you can define it manually and reference it in the annotation.

DARTRead-only
1
@Bloc(state: TodoState)
abstract class TodoBloc {
  @initialState()
  TodoState get initialState => const TodoState();
}

// Manually defined state
class TodoState extends Equatable {
  final List<Todo> todos;
  final bool isLoading;
  const TodoState({this.todos = const [], this.isLoading = false});
  // ...
}

IDE Integration & Custom Templates

You can create custom snippets or templates in your IDE to quickly scaffold a new bloc file. For VSCode, use the bloc extension (by Felix Angelov) which provides commands to generate a new bloc, cubit, or feature. This is an alternative to bloc_generator and works well for many developers.

Best Practices

  • Keep generated files out of version control – Add *.g.dart to .gitignore and regenerate on CI.
  • Use build_runner watch during development – Automatically regenerate when you modify the annotated file.
  • Don't manually edit generated files – They will be overwritten on next build.
  • Combine with Equatable and freezed – For complex states, you can use freezed alongside bloc_generator (some generators integrate with freezed).
  • Use consistent naming – The generator infers names from the methods; keep them clear.
  • Test generated blocs normally – The generated code is just standard BLoC code; unit tests work unchanged.

Common Mistakes

  • ❌ Forgetting the part directive – Without part 'file.g.dart', the generated code won't be linked.
  • ❌ Not running build_runner after changes – The generator needs to be run manually or with watch.
  • ❌ Using @Bloc on a class that is not abstract – The generator expects an abstract class.
  • ❌ Modifying generated files – Changes will be lost; always edit the annotated file.
  • ❌ Not adding bloc_annotations as a dependency – The annotations are needed at compile time.
  • ❌ Overcomplicating with code generation for simple blocs – Sometimes manual code is simpler; use generation when boilerplate is repetitive.

Conclusion

BLoC code generation significantly reduces the amount of boilerplate you need to write, allowing you to focus on business logic. By using bloc_generator and bloc_annotations (or IDE extensions), you can maintain a consistent, type-safe, and maintainable BLoC architecture across your Flutter projects. Adopt code generation for large apps or whenever you find yourself writing repetitive BLoC classes.

Try it yourself

// This example shows a manual BLoC (no code generation) for demo purposes.
// To use code generation, you would need build_runner and bloc_annotations.
// See the description for setup instructions.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:equatable/equatable.dart';

// ---------- Manual BLoC ----------
class CounterState extends Equatable {
  final int value;
  const CounterState(this.value);
  @override
  List<Object?> get props => [value];
}

abstract class CounterEvent extends Equatable {
  const CounterEvent();
  @override
  List<Object?> get props => [];
}

class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(const CounterState(0)) {
    on<Increment>((event, emit) => emit(CounterState(state.value + 1)));
    on<Decrement>((event, emit) => emit(CounterState(state.value - 1)));
  }
}

// ---------- App ----------
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (_) => CounterBloc(),
        child: CounterPage(),
      ),
    );
  }
}

class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Manual BLoC vs Code Gen')),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Counter: ${state.value}', style: TextStyle(fontSize: 32)),
                SizedBox(height: 20),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    ElevatedButton(
                      onPressed: () => context.read<CounterBloc>().add(Increment()),
                      child: Text('+'),
                    ),
                    SizedBox(width: 20),
                    ElevatedButton(
                      onPressed: () => context.read<CounterBloc>().add(Decrement()),
                      child: Text('-'),
                    ),
                  ],
                ),
                SizedBox(height: 40),
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Text(
                    'Note: This is a manual BLoC. To see code generation in action, follow the instructions in the article.',
                    textAlign: TextAlign.center,
                    style: TextStyle(fontSize: 12),
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

Test Your Knowledge

Q1
of 4

Which package provides the annotations for BLoC code generation?

A
bloc_generator
B
bloc_annotations
C
flutter_bloc
D
build_runner
Q2
of 4

What command do you run to generate code after adding annotations?

A
flutter pub get
B
flutter pub run build_runner build
C
flutter gen
D
dart compile
Q3
of 4

Which directive must be included in the annotated file to link the generated code?

A
import
B
export
C
part
D
library
Q4
of 4

Why should you avoid manually editing generated `.g.dart` files?

A
They are encrypted
B
They will be overwritten on next build
C
They cause runtime errors
D
They are not part of the project

Frequently Asked Questions

Do I need to use code generation to use BLoC?

No, it's completely optional. Code generation is a convenience tool; you can always write BLoCs manually.

What is the difference between `bloc_generator` and the VSCode extension?

bloc_generator uses annotations and build_runner to generate code programmatically. The VSCode extension (Bloc by Felix Angelov) provides snippets and commands to create files manually, without annotations.

Can I use code generation with Cubit?

Some generators support Cubits, but bloc_annotations focuses on BLoC. The VSCode extension can generate Cubits as well.

How do I handle complex state with freezed?

You can define your state using freezed and then reference it in the @Bloc annotation. Some advanced generators integrate with freezed to generate immutable states with copyWith.

What about performance? Does generated code affect runtime performance?

No, generated code is just regular Dart code; it has the same performance as manually written code.

Previous

bloc freezed

Next

bloc logging

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.