Open In App

BLoC Pattern Flutter

Last Updated : 24 Apr, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

In Flutter applications, the Flutter BLoC (Business Logic Component) is used to manage the state. It helps separate business logic from UI. It ensures that the user interface is not strongly liaison with the business logic, making the app more versatile. It is powerful since it allows the creation of all kinds of applications. For instance, the Flutter bloc is appropriate in both production and learning environments.

Key Concepts of BLoC

  1. Streams: Streams help in managing user actions and data, and how it flows through your app. In simple words, streams are like rivers that carry messages(messages can be anything, such as user actions or data updates).
  2. Sink: It's where you input data that you want to process. Data is poured into the sink, which then travels through it. A sink is like a place where you pour water into a stream.
  3. Stream Controller: A Stream Controller helps manage the streams, sinks and data, ensuring that data flows correctly through the system. In other words, it is a controller that controls the flow of water in the streams.
  4. Stream Builder: It updates the UI on the based of latest data in the stream. It updates the part of the app it's watching whenever new data comes down the stream through the sink. It's like a watchman that keeps an eye on the stream.

Installation of Flutter BLoC package

Step 1: Create a new Flutter Application

Create a new Flutter application using the command Prompt. To create a new app, write the following command and run it.

flutter create app_name

To know more about it refer this article: Creating a Simple Application in Flutter

Step 2: Add Dependencies

To add the dependency to the pubspec.yaml file, add  flutter_bloc as a dependency in the dependencies part of the pubspec.yaml file, as shown below:

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.1.6
  cupertino_icons: ^1.0.6


Now, run the below command in the terminal.

flutter pub get

Or

Run the below command in the terminal.

flutter pub add flutter_bloc


Import the BLoC Package in the Dart Code

To use the BLoC in your Flutter application, follow the given steps.

Step 1: Install the Bloc Extension

Install the following bloc extension in VSCode.

Note: If You are not using Vs Code or Don't want to automate the folder structure then, Skip this step and do it manually by creating every file.

Extension_-bloc---flutter_geeks


It will helps you to generate the boilerplate code and folder structure automatically, for that follow the steps to below.

- Create a folder

After installing the VSCode extension, create a new folder inside the lib folder and name it Home.

folder_structure

- Select New Bloc

Now, Right click on Home folder, it will display a option named 'Bloc: New Bloc' select that option.

New_Bloc

- Enter Bloc Name

After selecting New Bloc, it will ask for the name of the Bloc. Enter the New Bloc name as 'Home' and click on Enter!.

bloc_name

Then, it will create a new folder in your Home folder named bloc with all required files as image below.

Bloc_folder_structure

Step 2: Working with home_event.dart.

Note: If you are not following step1 create a folder structure manually as the image located above the Step 2.

Here we only have one event, CounterIncrementPressed to code, and that contains a parameter count because we need to pass the previous state before updating the state.

home_event.dart:

home_event.dart
part of 'home_bloc.dart';

/// Base class for all events related to the Home feature.
/// This class is immutable and sealed, meaning no other classes
/// can extend it outside of this file.
@immutable
sealed class HomeEvent {}

/// Event triggered when the counter increment button is pressed.
/// This extends the base `HomeEvent` class.
class CounterIncrementPressed extends HomeEvent {
  final int counter;

  CounterIncrementPressed({required this.counter});

}


Step 3: Working with home_state.dart

Here, also we only have one state to code and it also have one parameter counter because it is used to update state and emit to UI.

home_state.dart
part of 'home_bloc.dart';

/// Base class for all states in the Home feature.
/// This class is immutable and sealed, meaning no other classes
/// outside this file can extend it.
@immutable
sealed class HomeState {}

/// Initial state of the Home feature.
/// Represents the default state when no specific state is set.
final class HomeInitial extends HomeState {}

/// State representing the counter value.
/// Extends [HomeInitial] and holds the current counter value.
final class CounterState extends HomeInitial {
  /// The current value of the counter.
  final int counter;

  /// Constructor for [CounterState].
  /// Requires the [counter] value to be provided.
  CounterState({required this.counter});
}


Step 4: Working with home_bloc.dart

This class (HomeBloc) catches the triggered event (CounterIncrementPressed) with some previous state (event parameter in CounterIncrementPressed ) and updates it and emits through the state (CounterState(counter: event.counter + 1)) to UI.

home_bloc.dart:

home_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

// Importing the parts for event and state definitions
part 'home_event.dart';
part 'home_state.dart';

// The HomeBloc class extends Bloc and manages the state of the application
class HomeBloc extends Bloc<HomeEvent, CounterState> {
  // Constructor initializes the bloc with an initial state
  HomeBloc() : super(CounterState( counter: 0)) {
    // Specific event handler for CounterIncrementPressed
    on<CounterIncrementPressed>((event, emit) {
      // Emits a new CounterState with the incremented counter value
      emit(CounterState(counter: event.counter + 1));
    });
  }
}


Step 5: Working with UI

We develop a simple counter app UI with a FloatingActionButton and Text widget wrapped with Center to display the count.

-BlocProvider: Wrap the home class CounterPage() with BlocProvider, it is used initialize the state i.e, here the counter value '0' in home_bloc.dart.

Dart
// Providing the HomeBloc to the widget tree
home: BlocProvider(
    create: (context) => HomeBloc(),
    child: CounterPage(),
),


- Initialize the instance: Used to access events in home_event.dart to perform the trigger.

Dart
// Accessing the HomeBloc instance from the context
final HomeBloc counterBloc = BlocProvider.of<HomeBloc>(context);


- BlocBuilder: Wrap the Scaffold with BlocBuilder that contains builder as a parameter with (context, state), and here 'state' is used to get the updated state to UI throw "." operation, for example (state.count).

Dart
BlocBuilder<HomeBloc, CounterState>(
  // Rebuilding the UI based on the current state of HomeBloc
  builder: (context, state) {
    return Scaffold(
      
      body: Center(
        // Displaying the current counter value
        child: Text('Counter: ${state.counter}',
        style: TextStyle(
          fontSize: 24,
          fontWeight: FontWeight.bold,
        ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.blue.shade800,
        foregroundColor: Colors.white,
        onPressed: () {
          // Dispatching an event to increment the counter
          counterBloc.add(CounterIncrementPressed(counter: state.counter));
        },
        child: Icon(Icons.add), 
      ),
    );
  },
);


Full UI Code

main.dart:

main.dart
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_geeks/Home/bloc/home_bloc.dart';

void main() {
  // Entry point of the Flutter application
  runApp(MyApp());
}

// Root widget of the application
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      // Providing the HomeBloc to the widget tree
      home: BlocProvider(
        create: (context) => HomeBloc(),
        child: CounterPage(),
      ),
    );
  }
}

// Widget representing the Counter Page
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Accessing the HomeBloc instance from the context
    final HomeBloc counterBloc = BlocProvider.of<HomeBloc>(context);

    return BlocBuilder<HomeBloc, CounterState>(
      // Rebuilding the UI based on the current state of HomeBloc
      builder: (context, state) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Flutter BLoC Example'),
            backgroundColor: Colors.blue.shade800, 
            foregroundColor: Colors.white, 
          ),
          body: Center(
            // Displaying the current counter value
            child: Text('Counter: ${state.counter}',
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            backgroundColor: Colors.blue.shade800,
            foregroundColor: Colors.white,
            onPressed: () {
              // Dispatching an event to increment the counter
              counterBloc.add(CounterIncrementPressed(counter: state.counter));
            },
            child: Icon(Icons.add), 
          ),
        );
      },
    );
  }
}


Program Flow:

When the App started with the help of BlocProvider, the counter value was '0'.

BlocProvider(
create: (context) => HomeBloc(),
.....
)

As we are calling HomeBloc() class in home_bloc.dart and that calling CounterState( counter: 0) with '0' as counter value as below:

HomeBloc() : super(CounterState( counter: 0)) {
...
}

When it come to UI after that, BlocBuilder catching that state i.e, counter value '0' and it will display Zero at starting of the app.

BlocBuilder<HomeBloc, CounterState>(
// Rebuilding the UI based on the current state of HomeBloc
builder: (context, state) {
return Scaffold(
....
body: Text('Counter: ${state.counter}',
.....

When the user taps on the FloatingActionButton, it triggers the event.

FloatingActionButton(
onPressed: () {
// Dispatching an event to increment the counter
counterBloc.add(CounterIncrementPressed(counter: state.counter));
},
child: Icon(Icons.add),
),

Then it is taking current counter value and going to home_bloc.dart and searches for right on catch(on<CounterIncrementPressed>), if it finds then it will emit the state like CounterState(counter: event.counter +1 ), here with the event parameter we can easily access the previous state(event.counter) i.e, counter value and it implicitly updating the counter value and after that it is emitting that to UI.

on<CounterIncrementPressed>((event, emit) {
emit(CounterState(counter: event.counter + 1));
});

Then, BlocBuilder in the main.dart catching that updated state and displaying it in the UI.

BlocBuilder<HomeBloc, CounterState>(
// Rebuilding the UI based on the current state of HomeBloc
builder: (context, state) {
return Scaffold(
....
body: Text('Counter: ${state.counter}',
.....

This is what is happening in the whole code.

Complete Source Code

home_event.dart
part of 'home_bloc.dart';

/// Base class for all events related to the Home feature.
/// This class is immutable and sealed, meaning no other classes
/// can extend it outside of this file.
@immutable
sealed class HomeEvent {}

/// Event triggered when the counter increment button is pressed.
/// This extends the base `HomeEvent` class.
class CounterIncrementPressed extends HomeEvent {
  final int counter;

  CounterIncrementPressed({required this.counter});

}
home_state.dart
part of 'home_bloc.dart';

/// Base class for all states in the Home feature.
/// This class is immutable and sealed, meaning no other classes
/// outside this file can extend it.
@immutable
sealed class HomeState {}

/// Initial state of the Home feature.
/// Represents the default state when no specific state is set.
final class HomeInitial extends HomeState {}

/// State representing the counter value.
/// Extends [HomeInitial] and holds the current counter value.
final class CounterState extends HomeInitial {
  /// The current value of the counter.
  final int counter;

  /// Constructor for [CounterState].
  /// Requires the [counter] value to be provided.
  CounterState({required this.counter});
}
home_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

// Importing the parts for event and state definitions
part 'home_event.dart';
part 'home_state.dart';

// The HomeBloc class extends Bloc and manages the state of the application
class HomeBloc extends Bloc<HomeEvent, CounterState> {
  // Constructor initializes the bloc with an initial state
  HomeBloc() : super(CounterState( counter: 0)) {
    // Specific event handler for CounterIncrementPressed
    on<CounterIncrementPressed>((event, emit) {
      // Emits a new CounterState with the incremented counter value
      emit(CounterState(counter: event.counter + 1));
    });
  }
}
main.dart
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_geeks/Home/bloc/home_bloc.dart';

void main() {
  // Entry point of the Flutter application
  runApp(MyApp());
}

// Root widget of the application
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      // Providing the HomeBloc to the widget tree
      home: BlocProvider(
        create: (context) => HomeBloc(),
        child: CounterPage(),
      ),
    );
  }
}

// Widget representing the Counter Page
class CounterPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Accessing the HomeBloc instance from the context
    final HomeBloc counterBloc = BlocProvider.of<HomeBloc>(context);

    return BlocBuilder<HomeBloc, CounterState>(
      // Rebuilding the UI based on the current state of HomeBloc
      builder: (context, state) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Flutter BLoC Example'),
            backgroundColor: Colors.blue.shade800, 
            foregroundColor: Colors.white, 
          ),
          body: Center(
            // Displaying the current counter value
            child: Text('Counter: ${state.counter}',
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            backgroundColor: Colors.blue.shade800,
            foregroundColor: Colors.white,
            onPressed: () {
              // Dispatching an event to increment the counter
              counterBloc.add(CounterIncrementPressed(counter: state.counter));
            },
            child: Icon(Icons.add), 
          ),
        );
      },
    );
  }
}


Output:



Next Article

Similar Reads