🚀 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.
|
Define States with @generateStates
|
Define Events with @generateEvents
|
|
Use Generated Code - Simple Context Extension
|
|
🚀 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
- Quick Start
- Installation
- Core Features
- Usage Guide
- What Gets Generated
- Best Practices
- Migration Guide
- Contributing
- 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 @GenerateEvents |
Auto Generation
|
Built-in Safety
|
📖 Usage Guide
⚠️ IMPORTANT NOTE
When using@GenerateStates, you MUST callregisterEvents(this)in your bloc constructor.
Without this, thecontext.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
CounterStateclass with all propertiesCounterState.initial()factory constructorcopyWith()method for immutable updatescopyWithNull()for setting nullable fields to null- Auto-generated events for each property
context.setCounterBlocState()extension methodregisterEvents()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.dartcontaining 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 valuescopyWith()for immutable updatescopyWithNull()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
Use meaningful, descriptive names |
DON'T
Avoid cryptic variable names |
Event Naming
|
DO
Use descriptive names |
DON'T
Avoid generic names |
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
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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