Flutter Bloc Counter App for Absolute Beginners

Hey there! Welcome to this beginner-friendly tutorial on Flutter Bloc state management. We’re building a super simple Counter app using the flutter_bloc package. It’ll have two buttons—one to increase a number, one to decrease it. By the end, you’ll get how Bloc works, step-by-step. Let’s jump in!

What is Bloc?

Bloc stands for Business Logic Component. It helps separate your app’s logic—like increasing or decreasing a counter—from the UI, making your code cleaner and easier to manage.

The flutter_bloc package is a popular tool that makes this pattern easy to use in Flutter. For our Counter App, we’ll use Bloc to increment and decrement the counter efficiently.

Set Up Your Project

First, we need to set up our project. We’ll create a new Flutter app and add the flutter_bloc package. This package gives us the tools to use Bloc. In your terminal, type flutter create counter_bloc_app. Then, open pubspec.yaml. Under dependencies, we’ll add flutter_bloc and run flutter pub get to install it

To implement the BLoC (Business Logic Component) pattern in your Flutter project, follow these steps:

  1. Inside the lib folder, create a new file:

    • main.dart

    • counter_screen.dart (UI that interacts with the BLoC)
  2. Next, inside the lib folder, create a new directory called bloc:

    lib/bloc/
  3. Inside the bloc folder, create the following files:

    • counter_bloc.dart (Handles business logic and state management)

    • counter_event.dart (Defines user actions and events)

    • counter_state.dart (Manages different states of the counter)

These files will hold the core components of our app. Don’t worry—we’ll explain each one in detail! 🚀

Define Events

Now, let’s talk about events. Events are like commands you send to Bloc(Simply user’s actions on the app)—things you want to happen. In our Counter app, we only need two:

one to increase the count and one to decrease it. We’ll define these in counter_event.dart.

We’ll make an abstract class as a base, then add our two specific events. They don’t need extra info—just the instruction to go up or down. Here’s the code:

 

The abstract class CounterEvent is like a category for all our events. IncrementEvent and DecrementEvent are the specific actions—super simple

 
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

class DecrementEvent extends CounterEvent {}

The abstract class CounterEvent is like a category for all our events. IncrementEvent and DecrementEvent are the specific actions—super simple!

Define States

Next, let’s talk about state—the data our app displays. In simple terms, state represents the final output of a particular action.

For our Counter app, the state is just a number (like 0, 5, or any count value). We need to define this in counter_state.dart.

To manage this, we’ll create a class that holds this number. The BLoC will update the state, and the UI will display it accordingly.

 
class CounterState {
  final int count;
  CounterState(this.count);
}
CounterState is a simple class with one property: count. The final int count means it’s a number that won’t change withina state, but Bloc can give us a new state with a different number.

Create the Bloc

Now, let’s build the Bloc—it’s the brain that connects events to states. In counter_bloc.dart, we’ll set up a class that starts with a count of 0 and listens for our events. When it gets an IncrementEvent, it’ll add 1 to the count. For a DecrementEvent, it’ll subtract 1. We use emit to send the new state to the UI. Here’s the code:

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

class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) {
      emit(CounterState(state.count + 1));
    });

    on<DecrementEvent>((event, emit) {
      emit(CounterState(state.count - 1));
    });
  }
}
Let’s break it down: CounterBloc extends Bloc, which needs two types—our events and states. super(CounterState(0))sets the starting state to 0. The on<IncrementEvent> part says, ‘when you get this event, take the current count (state.count), add 1, and emit a new state.’ Same idea for DecrementEvent, but we subtract 1. The emit function is how we update the state.”

Build the UI

Time for the UI! In counter_screen.dart, we’ll show the count and add two buttons. We’ll use BlocBuilder to listen to the state and update the number on screen. The buttons will send events to the Bloc when tapped. It’s simple but powerful—here’s the code:

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

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter with Bloc')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            BlocBuilder<CounterBloc, CounterState>(
              builder: (context, state) {
                return Text(
                  '${state.count}',
                  style: TextStyle(fontSize: 48),
                );
              },
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () {
                    context.read<CounterBloc>().add(DecrementEvent());
                  },
                  child: Text('-'),
                ),
                SizedBox(width: 20),
                ElevatedButton(
                  onPressed: () {
                    context.read<CounterBloc>().add(IncrementEvent());
                  },
                  child: Text('+'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
Here’s why it works: BlocBuilder watches the CounterBloc and rebuilds the Text widget whenever the state changes, showing state.count. The onPressed for each button uses context.read<CounterBloc>().add() to send an event—DecrementEvent for ‘-‘ and IncrementEvent for ‘+’. The UI doesn’t change the number itself—Bloc does!

Tie It All Together in main.dart

Last step—let’s connect everything in main.dart. We need to provide the CounterBloc to our app so the UI can use it. We’ll wrap our app in a BlocProvider, which creates the Bloc and makes it available. Here’s the code:
 import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'counter_bloc.dart';
import 'counter_screen.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CounterBloc(),
      child: MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}
BlocProvider is like a delivery service—it gives CounterBloc to CounterScreen. The create: (context) => CounterBloc()line makes a new Bloc instance. Then, MaterialApp sets our screen as the home page. Run it with flutter run—you’re done!