Flutter BLoC for Beginners — Explained with Counter App
Let’s build a simple counter app using the BLoC pattern in Flutter. I’ll explain why we do each step and how it works, so you understand…
Flutter BLoC for Beginners — Explained with Counter App
Let’s build a simple counter app using the BLoC pattern in Flutter. I’ll explain why we do each step and how it works, so you understand the concepts clearly.
1. Why Use BLoC?
BLoC (Business Logic Component) is a state management pattern that helps: ✅ Separate business logic from UI (cleaner code) ✅ Manage state predictably (avoids bugs) ✅ Make code testable (easier to write unit tests) ✅ Handle complex state changes (scales well for bigger apps)
2. Project Setup
Step 1: Create a Flutter Project
flutter create counter_bloc_app
cd counter_bloc_app
Step 2: Add flutter_bloc Dependency
Open pubspec.yaml and add:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^8.1.3 # BLoC package for Flutter
or run this on terminal:
flutter pub add flutter_bloc
then: flutter pub get
3. BLoC Structure Explained
BLoC works with 3 main components:
- Events — What happens (e.g., button click → increment counter)
- States — What the app looks like (e.g., counter = 5)
- BLoC — Handles events and produces new states
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ UI │ │ Event │ │ BLoC │ │ State │
└───────────┘ └───────────┘ └───────────┘ └───────────┘
│ │ │ │
│ User clicks "+" │ │ │
│──────────────────▶│ │ │
│ │ │ │
│ │ IncrementEvent │ │
│ │──────────────────▶│ │
│ │ │ │
│ │ │ Compute +1 │
│ │ │───▶ new state │
│ │ │ │
│ │ │ Emit new state │
│ │ │──────────────────▶│
│ │ │ │
│ UI updates │ │ │
│◀──────────────────│ │ │
│ │ │ │
We’ll organize files in lib/bloc/:
bloc/
├── counter_bloc.dart # Business logic
├── counter_event.dart # Events (like Increment/Decrement)
└── counter_state.dart # States (like current counter value)
4. Defining Events (counter_event.dart) : Holds Events
What are Events?
- User actions (e.g., button clicks)
- Sent to BLoC to trigger state changes
Code (counter_event.dart)
part of 'counter_bloc.dart'; // Links to the BLoC file
abstract class CounterEvent {} // Base event class
class IncrementEvent extends CounterEvent {} // +1 event
class DecrementEvent extends CounterEvent {} // -1 eventd
- We define what can happen (increment/decrement).
- Events are simple classes (no logic here).
5. Defining State (counter_state.dart)
What is State?
- Represents how the app looks at any moment.
- In this case, just the counter value.
Code (counter_state.dart) : Holds States
part of 'counter_bloc.dart'; // Links to the BLoC file
class CounterState {
final int counterValue; // define the state variable: Stores the current count
const CounterState({required this.counterValue}); // Constructor to access this class
}
- The UI will read this state to display the counter.
- If counterValue changes, the UI updates automatically.
6. Implementing the BLoC (counter_bloc.dart)
What does BLoC do?
- Takes events (like IncrementEvent).
- Computes new state (e.g., counterValue + 1).
- Emits the new state so the UI updates.
Code (counter_bloc.dart) : Business logic
import 'package:flutter_bloc/flutter_bloc.dart';
part 'counter_event.dart';
part 'counter_state.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
//super constructor initialize the state with CounterState class
CounterBloc() : super(const CounterState(counterValue: 0)) {
// Handle IncrementEvent
on<IncrementEvent>((event, emit) {
emit(CounterState(counterValue: state.counterValue + 1));
});
// Handle DecrementEvent
on<DecrementEvent>((event, emit) {
emit(CounterState(counterValue: state.counterValue - 1));
});
}
}
- When the app starts, the initial state is counterValue: 0.
- If IncrementEvent is received:
- It takes the current state (state.counterValue).
- Adds 1 and emits a new state.
- The UI listens to state changes and updates.
┌─────────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐
│ │ │ │ │ │ │ │
│ UI (Widget) │───▶│ Event │───▶│ BLoC │───▶│ State │
│ │ │ (Increment/ │ │ (Logic) │ │ (Counter Value) │
│ [FAB Buttons] │ │ Decrement) │ │ │ │ │
└─────────────────┘ └─────────────┘ └─────────────┘ └─────────────────┘
▲ │
│ │
└───────────────────────────────────────────────────────
7. Building the UI (counter_page.dart)
How does UI interact with BLoC?
- Sends events when buttons are pressed (IncrementEvent/DecrementEvent).
- Listens to state changes using BlocBuilder.
Code (counter_page.dart)
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/counter_bloc.dart';
class CounterPage extends StatelessWidget {
const CounterPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Counter with BLoC')),
body: Center(
//1. the full app enclosed with BlocBuilder: to rebuild if needed
child: BlocBuilder<CounterBloc, CounterState>(
builder: (context, state) {
return Text(
'Counter: ${state.counterValue}',
style: const TextStyle(fontSize: 24),
);
},
),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
//2. sends event to [BLoc] that Increment Event happened
context.read<CounterBloc>().add(IncrementEvent()); // Send event
},
child: const Icon(Icons.add),
),
const SizedBox(height: 10),
FloatingActionButton(
onPressed: () {
context.read<CounterBloc>().add(DecrementEvent()); // Send event
},
child: const Icon(Icons.remove),
),
],
),
);
}
}
- BlocBuilder → Rebuilds UI when state changes.
- context.read<CounterBloc>().add(…) → Sends events to BLoC.
8. Connecting Everything (main.dart)
Why BlocProvider?
- Provides the CounterBloc to the widget tree.
- Ensures the same BLoC is available across the app.
Code (main.dart)
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/counter_bloc.dart';
import 'counter_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: BlocProvider(
create: (context) => CounterBloc(), // 1. Initialize BLoC
child: const CounterPage(), //2. Provide BLoC to the page
),
);
}
}
How it works?
- BlocProvider creates the CounterBloc and makes it available to CounterPage.
- Any widget inside CounterPage can access the BLoC using context.read<CounterBloc>().
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ UI │ │ Event │ │ BLoC │ │ State │
└───────────┘ └───────────┘ └───────────┘ └───────────┘
│ │ │ │
│ User clicks "+" │ │ │
│──────────────────▶│ │ │
│ │ │ │
│ │ IncrementEvent │ │
│ │──────────────────▶│ │
│ │ │ │
│ │ │ Compute +1 │
│ │ │───▶ new state │
│ │ │ │
│ │ │ Emit new state │
│ │ │──────────────────▶│
│ │ │ │
│ UI updates │ │ │
│◀──────────────────│ │ │
│ │ │ │
9. Final App Flow
1️⃣ User clicks “+” button → 2️⃣ IncrementEvent is sent to CounterBloc → 3️⃣ BLoC computes new state (counterValue + 1) → 4️⃣ New state is emitted → 5️⃣ BlocBuilder rebuilds UI with new value
Technical Flow:
Let’s say the current counter is 3, and the user presses the — button:
- Event Dispatched: context.read<CounterBloc>().add(DecrementEvent());
- BLoC Receives DecrementEvent:
- The on<DecrementEvent> handler runs.
3. New State Calculated:
- state.counter Value is 3, so 3–1 = 2.
4. New State Emitted:
- emit(CounterState(counterValue: 2)) updates the state.
5. UI Updates:
- BlocBuilder detects the new state (2) and rebuilds the Text widget.
Deep Dive:
1. on<DecrementEvent>(…) — Registering the Handler
- This line tells the BLoC: “When you receive a DecrementEvent, run this function.”
- It’s like setting up a listener for the DecrementEvent.
2. (event, emit) — The Handler Function Parameters
- event: The actual DecrementEvent object (not used here, but available if needed).
- emit: A function that sends (emits) a new state to update the app.
3. state.counterValue — 1 — Computing the New Value
- state.counterValue:
- state = the current state of the BLoC (before the event).
- counterValue = the current count (e.g., 5).
- Subtracting 1 gives us the new counter value (e.g., 5 → 4).
4. emit(CounterState(…)) — Updating the State
- CounterState(counterValue: …): Creates a new state with the updated value.
- emit(…):
- Sends this new state to all listeners (e.g., the UI).
- This triggers a rebuild of widgets that depend on this state.
Key Points
✅ Immutability: We never modify the old state directly. Instead, we create a new state every time. ✅ No Side Effects: The handler is a pure function — it only depends on the input (event and state). ✅ Automatic UI Sync: emit ensures the UI always reflects the latest state.
10. Key Takeaways
✔ Events = Actions (what happened) ✔ State = Data (what the app shows) ✔ BLoC = Logic (how state changes) ✔ UI = Listens to state & sends events
This structure keeps your code clean, scalable, and testable!
메타데이터
- post_id
- d5d1b52003b4
- slug
- flutter-bloc-for-beginners-explained-with-counter-app-d5d1b52003b4
- url
- https://medium.com/@mumin-ahmod/flutter-bloc-for-beginners-explained-with-counter-app-d5d1b52003b4
- canonical_url
- https://medium.com/@mumin-ahmod/flutter-bloc-for-beginners-explained-with-counter-app-d5d1b52003b4
- author_url
- https://medium.com/@mumin-ahmod
- status
- ok
- fetched_at
- 2026-07-06 21:57:15