Understanding the BLoC Pattern in Flutter: A Simple Counter App Example

Akshay Sawant
Nerd For Tech
Published in
4 min readJul 6, 2024

Managing event(action), state(situation/condition) and business logic(handler/manager) in your Flutter app can become complex as your app grows. The BLoC (Business Logic Component) pattern is a powerful way to separate your UI from your business logic, making your code more modular, reusable, and testable. In this blog post, we’ll explore the BLoC pattern using a simple counter app as an example.

Understanding the BLoC Pattern in Flutter: A Simple Counter App Example

What is the BLoC Pattern?

The BLoC pattern helps manage event and state in your app by separating business logic from UI components. Here’s how it works:

  1. Events: Actions that the user or system triggers, like pressing a button.
  2. States: The different conditions or configurations of the UI based on the events.
  3. BLoC: The component where the business logic resides. It listens to events, processes them, and emits new states.

BLoC as a Manager

  • Events: These are inputs that signify actions or occurrences in your app. For example, a user pressing a button, a page being loaded, or data being fetched from an API. Events are dispatched to the BLoC.
  • States: These are outputs that represent different conditions or configurations of the UI. For example, the app might be in a loading state, a loaded state with data, or an error state. The BLoC emits new states based on the events it processes.
  • BLoC: The BLoC itself is a class that contains the business logic of your application. It takes events as input, processes them, performs necessary actions (like fetching data or processing user input), and then outputs states. The BLoC essentially acts as a manager or intermediary that listens to events, handles business logic, and emits states that the UI listens to and reacts to.

How It Works

  1. Event Dispatched: When an event occurs, such as a button press, it is dispatched to the BLoC.
  2. Processing: The BLoC receives the event, processes it according to the business logic, and determines what the next state should be.
  3. State Emission: The BLoC emits the new state.
  4. UI Updates: The UI listens to state changes and updates accordingly based on the current state.

Key Concepts

  • Events: Represent user actions or system triggers (e.g., button press).
  • States: Represent the UI’s condition based on events (e.g., loading, loaded, error).
  • BLoC: Contains the business logic to handle events and emit states.

In Simple Terms

Imagine you’re managing a traffic light system:

  • Events: Cars arriving at the light (event types: car arrives, car leaves).
  • States: The traffic light can be in one of several states (red, yellow, green).
  • BLoC (Traffic Light Controller): This is the manager that decides how to change the light based on the events. When a car arrives (event), the BLoC processes it and changes the light to green (state). After some time or when the car leaves (event), it processes this and changes the light to yellow or red (state).

Setting Up the BLoC Pattern in Flutter

Let’s create a simple counter app to illustrate the BLoC pattern. The app will have buttons to increment and decrement a counter.

Step 1: Add Dependencies

First, add the necessary dependencies to your pubspec.yaml file:

dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.0.0
bloc: ^8.0.0

Step 2: Define Events

Create a file named counter_event.dart:

// counter_event.dart
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}class DecrementEvent extends CounterEvent {}

Step 3: Define States

Create a file named counter_state.dart:

// counter_state.dart
class CounterState {
final int counter;
CounterState(this.counter);
}

Step 4: Create the BLoC

Create a file named counter_bloc.dart:

// counter_bloc.dart
import 'package:bloc/bloc.dart';
import 'counter_event.dart';
import 'counter_state.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState(0));
@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
if (event is IncrementEvent) {
yield CounterState(state.counter + 1);
} else if (event is DecrementEvent) {
yield CounterState(state.counter - 1);
}
}
}

Step 5: Create the UI

In your main.dart file, set up the UI to interact with the BLoC:

// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
import 'counter_event.dart';
import '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 counterBloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Counter App')),
body: Center(
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text('Counter: ${state.counter}', style: TextStyle(fontSize: 24));
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: () => counterBloc.add(IncrementEvent()),
child: Icon(Icons.add),
),
SizedBox(height: 8),
FloatingActionButton(
onPressed: () => counterBloc.add(DecrementEvent()),
child: Icon(Icons.remove),
),
],
),
);
}
}

Summary

By following these steps, you’ve created a simple counter app using the BLoC pattern. This approach allows you to:

  1. Separate Business Logic from UI: Keep your code modular and maintainable.
  2. Reuse Components: Easily reuse business logic across different parts of your app.
  3. Test Easily: Test your business logic independently from your UI.

The BLoC pattern is a powerful tool for managing state in Flutter apps. By separating your business logic from your UI, you can create cleaner, more maintainable, and testable code. Happy coding!

--

--