Mastering Flutter BLoC: Best Practices for State, Event, and BLoC Folders

Mastering Flutter BLoC: Best Practices for State, Event, and BLoC Folders

TB

Teqani Blogs

Writer at Teqani

July 16, 20253 min read

Managing state in Flutter apps becomes significantly more manageable and scalable when leveraging the BLoC (Business Logic Component) pattern. A clean and efficient way to structure your BLoC code is by organizing it into three distinct folders: event, state, and bloc. This article demonstrates how to implement this folder structure and provides a simple counter example.



Why Organize with State, Event, and BLoC Folders?



  • event: Contains all possible actions or events (like button presses).
  • state: Holds the different states your UI can display.
  • bloc: Contains the logic for handling events and producing new states.


This structure ensures your project remains modular, readable, and easily scalable as your app grows.



Folder Structure



Here’s how your lib folder might look:



lib/
└── bloc/
    └── counter_bloc.dart
└── event/
    └── counter_event.dart
└── state/
    └── counter_state.dart


Step 1: Define Events



Inside event/counter_event.dart:



abstract class CounterEvent {}

class Increment extends CounterEvent {}

class Decrement extends CounterEvent {}


Step 2: Define State



Inside state/counter_state.dart:



class CounterState {
  final int counterValue;

  CounterState(this.counterValue);
}


Step 3: Create the BLoC



Inside bloc/counter_bloc.dart:



import 'package:flutter_bloc/flutter_bloc.dart';
import '../event/counter_event.dart';
import '../state/counter_state.dart';

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


Step 4: Connect BLoC to the UI



Here’s a minimal main.dart to tie it all together:



import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/counter_bloc.dart';
import 'event/counter_event.dart';
import 'state/counter_state.dart';

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) {
    final bloc = BlocProvider.of<CounterBloc>(context);
    return Scaffold(
      appBar: AppBar(title: Text('Flutter BLoC Counter')),
      body: Center(
        child: BlocBuilder<CounterBloc, CounterState>(
          builder: (context, state) =>
              Text('${state.counterValue}', style: TextStyle(fontSize: 48)),
        ),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: "decrement",
            onPressed: () => bloc.add(Decrement()),
            child: Icon(Icons.remove),
          ),
          SizedBox(width: 16),
          FloatingActionButton(
            heroTag: "increment",
            onPressed: () => bloc.add(Increment()),
            child: Icon(Icons.add),
          ),
        ],
      ),
    );
  }
}


Core BLoC Keywords & Classes



These are the fundamental classes provided by the bloc and flutter_bloc packages:



  1. Bloc 📌 The core class for business logic

    Requires Event (input) & State (output). Uses mapEventToState for reactive state changes.



  2. Cubit ⚡ A lightweight alternative to Bloc

    Uses functions (e.g., emit()) instead of events. Simpler syntax, ideal for basic state management.



  3. BlocProvider 🌐 Dependency Injection for Bloc/Cubit

    A Flutter widget that provides a Bloc/Cubit to its children. Ensures a single instance is available down the widget tree.



  4. BlocBuilder 🔄 Reactive UI Updates

    Rebuilds only the necessary widgets when state changes. Optimized for performance with buildWhen.



  5. BlocListener 🎧 Side Effects Handler

    Calls callbacks (e.g., navigation, dialogs) on state changes. Does not rebuild UI (ideal for one-time actions).



  6. BlocConsumer 🎭 Best of Both Worlds

    Combines BlocBuilder (UI updates) + BlocListener (side effects). Reduces boilerplate for common use cases.



  7. MultiBlocProvider 🧩 Multi-Bloc Dependency Injection

    Provides multiple Blocs to a subtree in a clean way. Avoids nested BlocProvider widgets.



  8. RepositoryProvider 🗃️ Clean Architecture Companion

    Supplies a repository (data layer) to the widget tree. Works seamlessly Bloc for the separation of concerns.



Event & State Management



  1. Event ⚡ Triggers state changes in your Bloc

    Represents user actions or app triggers (e.g., LoginButtonPressed, FetchUserData). Events are added to the Bloc via add(event).



  2. State 📊 Represents the app’s condition at any moment

    Examples:



    • LoginInitial → No action yet.
    • LoginLoading → API call in progress.
    • LoginSuccess → User logged in.
    • LoginFailure → Error occurred.


    The UI reacts to state changes.



  3. emit() 🚀 Sends a new state from Bloc/Cubit

    Used inside Bloc/Cubit to update the state.



    // Example (Cubit):
    void login() {
      emit(LoginLoading());
      // ... API call ...
      emit(LoginSuccess(user));
    }


Example Snippet Using Key BLoC Keywords



// 1. Define Events
abstract class CounterEvent {}
class Increment extends CounterEvent {}
class Decrement extends CounterEvent {}

// 2. Define States
class CounterState extends Equatable {
  final int count;
  const CounterState(this.count);

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

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

// 4. Integrate with Flutter UI
BlocProvider(
  create: (context) => CounterBloc(),
  child: BlocBuilder<CounterBloc, CounterState>(
    builder: (context, state) {
      return Text('Count: ${state.count}');
    },
  ),
);


Conclusion



Organizing your Flutter BLoC code into state, event, and bloc Folders helps keep your codebase clean and scalable. This approach is easy to follow and works great for both small and large projects.

TB

Teqani Blogs

Verified
Writer at Teqani

Senior Software Engineer with 10 years of experience

July 16, 2025
Teqani Certified

All blogs are certified by our company and reviewed by our specialists
Issue Number: #e90babef-4c03-4f85-b4a9-6918c4919c72