🚀 Flutter Bloc Generator

A revolutionary Flutter package that eliminates verbose boilerplate in BLoC pattern development. Generate complete STATE, EVENT, and BLOC implementations from a single line of state variable declaration. With support for custom event classes through factory constructors, it dramatically reduces development time while maintaining type safety and best practices.

Flutter Bloc Generator Banner


Pub Version GitHub Discord

Define States with @generateStates

@generateStates
abstract class _$$BaseState {
  final bool active = false;
  final bool? isLoading = false;
  final bool isError = false;
}

Define Events with @generateEvents


@generateEvents
abstract class BaseEvent extends Equatable {
  const BaseEvent();
}

Use Generated Code - Simple Context Extension

ElevatedButton(
  onPressed: () {
    context.setBaseBlocState(active: isActive);
  },
  child: Text('Active'),
), // ElevatedButton

🚀 Why Choose fbloc_event_gen?

VSCode Extension Available!

Get the Fbloc Event Gen extension from Visual Studio Marketplace to supercharge your development workflow!

Key Benefits

  • Zero Boilerplate: Generate all state, event, and even bloc code from a SINGLE VARIABLE
  • Simple State Updates: Update state without remembering event names - just use context.set{BlocName}State()
  • Fully Customizable: All options to customize your bloc and event code without hassle
  • Type-Safe: Automatic type checking and null safety support
  • Production Ready: Built for apps of any scale - from small projects to large enterprise applications

Community

Join our growing community on Discord to get support, share feedback, and stay updated with the latest features!

📚 Table of Contents

  1. Quick Start
  2. Installation
  3. Core Features
  4. Usage Guide
  5. What Gets Generated
  6. Best Practices
  7. Migration Guide
  8. Contributing
  9. License

⚡ Quick Start

Get up and running in just 3 steps!

Step 1: Add Dependencies

dependencies:
  fbloc_event_gen: ^3.2.7
  equatable: ^2.0.7
  flutter_bloc: # Any Bloc Version

dev_dependencies:
  build_runner: ^2.4.6

Step 2: Create Your Files

counter_bloc.dart (main file)

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fbloc_event_gen/annotations.dart';
import 'package:equatable/equatable.dart';

part 'counter_event.dart';
part 'counter_state.dart';
part 'counter_bloc.g.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState.initial()) {
    // ⚠️ IMPORTANT: Register auto-generated events
    registerEvents(this);
  }
}

counter_state.dart

part of 'counter_bloc.dart';

@generateStates
abstract class _$$CounterState {
  final int count = 0;
  final bool isLoading = false;
}

counter_event.dart

part of 'counter_bloc.dart';

@generateEvents
abstract class CounterEvent extends Equatable {
  const CounterEvent();
}

Step 3: Run Code Generation

dart run build_runner build --delete-conflicting-outputs --use-polling-watcher

That's it! Now use context.setCounterBlocState(count: 5) anywhere in your widgets! 🎉


📦 Installation

Add to pubspec.yaml

dependencies:
  fbloc_event_gen: ^3.2.7
  equatable: ^2.0.7
  flutter_bloc:   # Any Bloc Version above Required for BLoC pattern

dev_dependencies:
  build_runner: ^2.4.6

Install Packages

flutter pub get

Setup Build Runner (Optional - Watch Mode)

For automatic code generation on file changes:

dart run build_runner watch --delete-conflicting-outputs --use-polling-watcher

Or for one-time generation:

dart run build_runner build --delete-conflicting-outputs

✨ Core Features

Two Annotations

@GenerateStates
Complete state management

@GenerateEvents
Custom event classes

Auto Generation

  • Type-safe events
  • Immutable states
  • Context extensions
  • copyWith methods

Built-in Safety

  • Null safety
  • Equatable support
  • Type checking
  • Documentation

📖 Usage Guide

⚠️ IMPORTANT NOTE
When using @GenerateStates, you MUST call registerEvents(this) in your bloc constructor.
Without this, the context.set{YourBloc}State() extension won't work!

class YourBloc extends Bloc<YourEvent, YourState> {
  YourBloc() : super(YourState.initial()) {
     registerEvents(this);  // ← Don't forget this!
  }
}

Using @GenerateStates

@GenerateStates is your all-in-one solution for complete state management. Simply define your state variables with initial values, and let the generator create everything else!

📝 Define Your State (counter_state.dart)

part of 'counter_bloc.dart';

@generateStates
abstract class _$$CounterState {
  final int counter = 0;
  final bool isLoading = false;
  final String? message = null;
  final List<String> items = const [];
  final Map<String, dynamic> data = const {};
}

📝 Create Your Bloc (counter_bloc.dart - main file)

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fbloc_event_gen/annotations.dart';
import 'package:equatable/equatable.dart';

part 'counter_event.dart';
part 'counter_state.dart';
part 'counter_bloc.g.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState.initial()) {
    // ⚠️ CRITICAL: This line is REQUIRED for context.setCounterBlocState() to work
    registerEvents(this);
  }
}

📝 Define Your Event (counter_event.dart)

part of 'counter_bloc.dart';

@generateEvents
abstract class CounterEvent extends Equatable {
  const CounterEvent();
}

What You Get

  • CounterState class with all properties
  • CounterState.initial() factory constructor
  • copyWith() method for immutable updates
  • copyWithNull() for setting nullable fields to null
  • Auto-generated events for each property
  • context.setCounterBlocState() extension method
  • registerEvents() method for easy bloc setup
  • Full Equatable implementation

Using @GenerateEvents

@GenerateEvents is perfect when you need custom event classes with factory constructors. Great for complex user actions!

📝 Main Bloc File (auth_bloc.dart)

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fbloc_event_gen/annotations.dart';
import 'package:equatable/equatable.dart';

part 'auth_event.dart';
part 'auth_state.dart';
part 'auth_bloc.g.dart';

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  AuthBloc() : super(AuthState.initial()) {
    registerEvents(this);
    on<LoginEvent>(_onLogin);
    on<LogoutEvent>(_onLogout);
  }
  
  void _onLogin(LoginEvent event, Emitter<AuthState> emit) {
    // Your login logic
  }
  
  void _onLogout(LogoutEvent event, Emitter<AuthState> emit) {
    // Your logout logic
  }
}

📝 Define Your Events (auth_event.dart)

part of 'auth_bloc.dart';

@generateEvents
abstract class AuthEvent extends Equatable {
  const AuthEvent();
  
  // Login event with required and optional parameters
  const factory AuthEvent.login({
    required String email,
    required String password,
    bool? rememberMe,
  }) = LoginEvent;
  
  // Simple logout event
  const factory AuthEvent.logout() = LogoutEvent;
  
  // Update profile event
  const factory AuthEvent.updateProfile({
    required String name,
    String? avatarUrl,
  }) = UpdateProfileEvent;
}

📝 Define Your State (auth_state.dart)

part of 'auth_bloc.dart';

@generateStates
abstract class _$$AuthState {
  final bool isAuthenticated = false;
  final String? userId = null;
  final String? token = null;
}

What You Get

  • Complete event classes (LoginEvent, LogoutEvent, etc.)
  • Proper constructors with required/optional parameters
  • Equatable implementation with props
  • Immutable and type-safe events

Complete Implementation

Here's a full example showing how everything works together:

1. Main Bloc File (counter_bloc.dart)

import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fbloc_event_gen/annotations.dart';
import 'package:equatable/equatable.dart';

part 'counter_event.dart';
part 'counter_state.dart';
part 'counter_bloc.g.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState.initial()) {
    // Register auto-generated state update events
    registerEvents(this);
    
    // Handle custom events
    on<IncrementEvent>(_onIncrement);
    on<DecrementEvent>(_onDecrement);
    on<ResetEvent>(_onReset);
  }
  
  void _onIncrement(IncrementEvent event, Emitter<CounterState> emit) {
    emit(state.copyWith(count: state.count + 1));
  }
  
  void _onDecrement(DecrementEvent event, Emitter<CounterState> emit) {
    emit(state.copyWith(count: state.count - 1));
  }
  
  void _onReset(ResetEvent event, Emitter<CounterState> emit) {
    emit(state.copyWith(count: 0));
  }
}

2. State File (counter_state.dart)

part of 'counter_bloc.dart';

@generateStates
abstract class _$$CounterState {
  final int count = 0;
  final bool isLoading = false;
  final String? errorMessage = null;
}

3. Event File (counter_event.dart)

part of 'counter_bloc.dart';

@generateEvents
abstract class CounterEvent extends Equatable {
  const CounterEvent();
  
  const factory CounterEvent.increment() = IncrementEvent;
  const factory CounterEvent.decrement() = DecrementEvent;
  const factory CounterEvent.reset() = ResetEvent;
}

💡 File Structure: These 3 files will generate counter_bloc.g.dart containing all the generated code.

4. Use in Widget

class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // Display state
        BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) {
            return Text('Count: ${state.count}');
          },
        ),
        
        // Traditional way - using custom events
        ElevatedButton(
          onPressed: () => context.read<CounterBloc>().add(
            const CounterEvent.increment()
          ),
          child: Text('Increment'),
        ),
        
        // Generated way - direct state updates
        ElevatedButton(
          onPressed: () => context.setCounterBlocState(
            count: 100,
            isLoading: true,
          ),
          child: Text('Set to 100'),
        ),
      ],
    );
  }
}

🔍 What Gets Generated

Understanding what code is generated helps you leverage the full power of fbloc_event_gen!

For @GenerateEvents

When you define:

@generateEvents
abstract class AuthEvent extends Equatable {
  const AuthEvent();
  
  const factory AuthEvent.login({
    required String email,
    required String password,
    bool? rememberMe,
  }) = LoginEvent;
  
  const factory AuthEvent.logout() = LogoutEvent;
}

You automatically get:

// **************************************************************************
// EventGenerator
// **************************************************************************

class LoginEvent extends AuthEvent {
  final String email;
  final String password;
  final bool? rememberMe;

  const LoginEvent({
    required this.email,
    required this.password,
    this.rememberMe,
  });

  @override
  List<Object?> get props => [email, password, rememberMe];
}

class LogoutEvent extends AuthEvent {
  const LogoutEvent();

  @override
  List<Object?> get props => [];
}

Benefits: Type-safe, immutable events with automatic Equatable implementation!


For @GenerateStates

When you define:

@generateStates
abstract class _$$CounterState {
  final int count = 0;
  final bool isLoading = false;
  final String? message = null;
}

You automatically get:

🎯 State Class with Auto-Generated Events
// Auto-generated events for each state property
class UpdateCountEvent extends CounterEvent {
  final int count;
  const UpdateCountEvent({required this.count});

  @override
  List<Object?> get props => [count];
}

class UpdateIsLoadingEvent extends CounterEvent {
  final bool isLoading;
  const UpdateIsLoadingEvent({required this.isLoading});

  @override
  List<Object?> get props => [isLoading];
}

class UpdateMessageEvent extends CounterEvent {
  final String? message;
  const UpdateMessageEvent({required this.message});

  @override
  List<Object?> get props => [message];
}
📦 Complete State Class
class CounterState extends Equatable {
  final int count;
  final bool isLoading;
  final String? message;

  const CounterState({
    required this.count,
    required this.isLoading,
    this.message,
  });

  // Factory constructor for initial state
  static CounterState initial() {
    return const CounterState(
      count: 0,
      isLoading: false,
      message: null,
    );
  }

  // copyWith method for immutable updates
  CounterState copyWith({
    int? count,
    bool? isLoading,
    String? message,
  }) {
    return CounterState(
      count: count ?? this.count,
      isLoading: isLoading ?? this.isLoading,
      message: message ?? this.message,
    );
  }

  // copyWithNull for nullable fields
  CounterState copyWithNull({bool message = false}) {
    return CounterState(
      count: this.count,
      isLoading: this.isLoading,
      message: message ? null : this.message,
    );
  }



  @override
  List<Object?> get props => [count, isLoading, message];
}

// Auto-registers event handlers
 void registerEvents(CounterBloc bloc) {
    bloc.on<UpdateCountEvent>((event, emit) {
      emit(bloc.state.copyWith(count: event.count));
    });

    bloc.on<UpdateIsLoadingEvent>((event, emit) {
      emit(bloc.state.copyWith(isLoading: event.isLoading));
    });

    bloc.on<UpdateMessageEvent>((event, emit) {
      if (event.message == null) {
        emit(bloc.state.copyWithNull(message: true));
      } else {
        emit(bloc.state.copyWith(message: event.message));
      }
    });
  }
🔧 BuildContext Extension
extension CounterBlocContextExtension on BuildContext {
  /// Updates the CounterBloc state with the provided values.
  /// Only specified parameters will be updated; others remain unchanged.
  void setCounterBlocState({
    Object? count = UnspecifiedDataType.instance,
    Object? isLoading = UnspecifiedDataType.instance,
    Object? message = UnspecifiedDataType.instance,
  }) {
    final bloc = read<CounterBloc>();
    
    if (count != UnspecifiedDataType.instance) {
      bloc.add(UpdateCountEvent(count: count as int));
    }

    if (isLoading != UnspecifiedDataType.instance) {
      bloc.add(UpdateIsLoadingEvent(isLoading: isLoading as bool));
    }

    if (message != UnspecifiedDataType.instance) {
      bloc.add(UpdateMessageEvent(message: message as String?));
    }
  }
}

Benefits:

  • Complete state class with all properties
  • initial() factory with default values
  • copyWith() for immutable updates
  • copyWithNull() for nullable fields
  • Auto-generated update events
  • registerEvents() for easy setup
  • Context extensions for easy state updates
  • Full documentation comments

💡 Best Practices

State Management

DO

@generateStates
abstract class _$$UserState {
  final String username = '';
  final bool isAuthenticated = false;
  final String? profileUrl = null;
}

Use meaningful, descriptive names
Provide default values
Consider nullability carefully

DON'T

@generateStates
abstract class _$$UserState {
  final String u = '';
  final bool b = false;
  final String? x;
}

Avoid cryptic variable names
Don't skip default values
Don't over-use nullable types

Event Naming

DO

@generateEvents
abstract class AuthEvent {
  const factory AuthEvent.loginWithEmail({
    required String email,
    required String password,
  }) = LoginWithEmailEvent;
}

Use descriptive names
Follow verb-noun pattern
Group related events

DON'T

@generateEvents
abstract class AuthEvent {
  const factory AuthEvent.e1({
    String? x,
    String? y,
  }) = Event1;
}

Avoid generic names
Don't use numbered events
Missing documentation

When to Use What

Use Case Recommendation Why?
Simple state updates context.set{Bloc}State() Cleaner, less code
Complex business logic Custom events + handlers Better separation of concerns
Multiple state changes copyWith() in bloc Atomic state updates
Nullable field reset copyWithNull() Explicitly set to null
Initial setup {State}.initial() Consistent defaults

File Organization

lib/
├── blocs/
│   ├── auth/
│   │   ├── auth_bloc.dart        # Main bloc file (imports, bloc class)
│   │   ├── auth_event.dart       # Event definitions with @generateEvents
│   │   ├── auth_state.dart       # State definitions with @generateStates
│   │   └── auth_bloc.g.dart      # Generated file (auto-generated events + states)
│   └── counter/
│       ├── counter_bloc.dart     # Main bloc file (imports, bloc class)
│       ├── counter_event.dart    # Event definitions with @generateEvents
│       ├── counter_state.dart    # State definitions with @generateStates
│       └── counter_bloc.g.dart   # Generated file (auto-generated events + states)

File Structure Breakdown:

File Purpose Contains
{name}_bloc.dart Main file Imports, part declarations, Bloc class
{name}_state.dart State definition part of + @generateStates
{name}_event.dart Event definition part of + @generateEvents
{name}_bloc.g.dart Generated code Auto-generated events, states, extensions

🔄 Migration Guide

Migrating from 2.x to 3.x

Step 1: Update Dependencies

dependencies:
  fbloc_event_gen: ^3.2.7  # Update version

Step 2: Update State Class Names

Before:

@generateStates
abstract class MyState {
  // ...
}

After:

@generateStates
abstract class _$$MyState {  // Add _$$ prefix
  // ...
}

Step 3: Add Initial Values

Before:

abstract class _$$MyState {
  final int counter;
  final bool isLoading;
}

After:

abstract class _$$MyState {
  final int counter = 0;           // Add defaults
  final bool isLoading = false;     // Add defaults
}

Step 4: Run Code Generation

dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs

🤝 Contributing

We love contributions! 💙

How to Contribute

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Ways to Contribute

  • Report bugs
  • Suggest new features
  • Improve documentation
  • Add tests
  • Fix issues

See our Contributing Guide for more details.


📝 License

This project is licensed under the MIT License - see the LICENSE file for details.


🙏 Acknowledgments

Special thanks to all our contributors and the Flutter community for their support!

Show Your Support

If you like this package, please give it a ⭐ on GitHub!

Get in Touch


Made with ❤️ for the Flutter Community

Libraries

annotations
builder