Understanding Flutter BLoC: The Ultimate Guide to Managing App State

Prashant BalePrashant Bale
6 min read

As a mobile developer, one of the most challenging tasks is managing app state. State management is the backbone of every Flutter application. If your app’s state is not properly managed, it can get messy, and debugging becomes a nightmare. To keep things neat and maintainable, Flutter introduced a pattern called BLoC (Business Logic Component).

In this blog, we’ll dive deep into Flutter BLoC. Don’t worry if you’ve never heard of it before! I’ll explain it in simple, easy-to-follow steps. After reading this, you’ll understand what BLoC is, how to implement it, and why it’s beneficial for your Flutter apps. I’ll also include code snippets that you can directly use in your projects!


What is BLoC?

At its core, BLoC stands for Business Logic Component. In simple terms, it’s a design pattern used in Flutter to manage the state and business logic separately from the UI layer. This keeps your app’s code neat, organized, and scalable.

Why do we need BLoC?

When you build apps, you often need to handle:

  • User inputs (like button clicks)

  • Backend data (like API calls)

  • Local state changes (like loading, error, or success states)

BLoC helps you manage all of these in one place, making sure that your UI only shows what the state is at any given moment without worrying about the logic behind it.

In short: BLoC = Clean architecture + Better state management + Easier testing.


How Does BLoC Work?

Let’s break it down into the basics:

  • Events: These are actions that happen in the app. Events could be a user pressing a button, a network call completing, or any other action.

  • States: States represent different statuses or conditions in your app. For example, Loading, Error, Success, or Initial states.

  • BLoC: The BLoC is responsible for taking an event, processing it, and emitting a new state based on that event.

So, the flow looks like this:

  1. User triggers an event (e.g., pressing a button).

  2. The BLoC processes the event.

  3. The BLoC sends a new state (e.g., data loaded, error occurred).

  4. UI listens to the state and updates the screen accordingly.


Benefits of Using BLoC

  1. Separation of Concerns: Your UI is only responsible for displaying data, while the BLoC handles the app's logic. This makes your code cleaner and easier to manage.

  2. Better Testability: With BLoC, you can easily write unit tests for your business logic since it is separate from the UI.

  3. Scalable: As your app grows, the BLoC pattern helps in keeping things modular and scalable. You can add more features without making the code messy.

  4. Reusability: Since business logic is separate from the UI, you can reuse the same logic across different parts of your app.


How to Implement BLoC in Flutter

Now, let's get into the step-by-step implementation of BLoC in Flutter. We’ll build a simple counter app where the counter value can be increased or decreased.

Define Events for Connectivity

In Flutter Bloc, events are the actions or triggers that initiate state changes. Here, we’ll define two events: InternetGainedEvent and InternetLostEvent.

abstract class InternetEvent {}

class InternetGainedEvent extends InternetEvent {}

class InternetLostEvent extends InternetEvent {}

Define States for Connectivity

States represent the UI's current condition. In this case, we’ll have:

  • InternetInitialState: The initial state before the app checks connectivity.

  • InternetLostState: When there is no internet connection.

  • InternetGainedState: When there is internet access.

abstract class InternetState {}

class InternetInitialState extends InternetState {}

class InternetLostState extends InternetState {}

class InternetGainedState extends InternetState {}

Define the Bloc for Connectivity

The Bloc is responsible for converting events into states. It listens for changes in the internet connectivity status and emits the corresponding states.

import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:my_demo/bloc/internet_event.dart';
import 'package:my_demo/bloc/internet_state.dart';
import 'package:connectivity/connectivity.dart';

// Step 1: Define the InternetBloc class
class InternetBloc extends Bloc<InternetEvent, InternetState> {
  final Connectivity _connectivity = Connectivity();
  StreamSubscription? connectivitySubscription;

  // Constructor: Initializes the Bloc and listens to internet events
  InternetBloc() : super(InternetInitialState()) {
    // Step 2: Handling InternetGainedEvent
    on<InternetGainedEvent>((event, emit) {
      emit(InternetGainedState());
    });

    // Step 3: Handling InternetLostEvent
    on<InternetLostEvent>((event, emit) {
      emit(InternetLostState());
    });

    // Step 4: Monitor connectivity changes
    connectivitySubscription =
        _connectivity.onConnectivityChanged.listen((result) {
      if (result == ConnectivityResult.mobile ||
          result == ConnectivityResult.wifi) {
        add(InternetGainedEvent());
      } else {
        add(InternetLostEvent());
      }
    });
  }

  @override
  Future<void> close() {
    connectivitySubscription?.cancel();
    return super.close();
  }
}

Create the UI to Display Connectivity Status

Now that we have our Bloc set up, let’s create a UI that responds to the connectivity status changes. We will use BlocBuilder to listen to the InternetBloc and update the UI accordingly.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:my_demo/bloc/internet_bloc.dart';
import 'package:my_demo/bloc/internet_state.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Center(child:
            BlocBuilder<InternetBloc, InternetState>(builder: (context, state) {
          if (state is InternetGainedState) {
            return Text('Internet is connected');
          } else if (state is InternetLostState) {
            return Text('Internet is Lost');
          } else {
            return Text('Loading...');
          }
        })),
      ),
    );
  }
}

Setup BlocProvider in the Main App

In the main.dart file, wrap the entire app in a BlocProvider so that the InternetBloc is available to all widgets in the app.

dartCopyimport 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:my_demo/home_screen.dart';
import 'package:my_demo/bloc/internet_bloc.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => InternetBloc(),
      child: const MaterialApp(
        debugShowCheckedModeBanner: false,
        home: HomeScreen(),
      ),
    );
  }
}

You can access the complete code for this example on my GitHub repository: flutter_bloc_demo.


Best Practices for Using BLoC

Now that we’ve implemented BLoC, here are some best practices to follow:

  1. Keep Events and States Simple: Avoid making your events and states too complex. Each event should do only one thing, and each state should represent a clear condition.

  2. Use flutter_bloc Package: This package simplifies working with BLoC. It provides utilities like BlocProvider, BlocBuilder, and BlocListener.

  3. Don’t Overuse Streams: BLoC is great for complex state management, but if you have simple states, consider using a simpler solution like Provider.

  4. Test Your BLoC: Since the business logic is separated from the UI, it’s easy to write unit tests for your BLoC. Always test your BLoC to make sure your business logic is correct.

  5. Use Cubit for Simpler Scenarios: If you don't need the complexity of full BLoC, consider using Cubit, a simpler version of BLoC.


Tips and Tricks to Master BLoC

  1. Use BlocConsumer for Side Effects: If you need to perform side effects like showing a dialog or navigating to a new page, use BlocConsumer instead of BlocBuilder.

  2. StreamController for More Control: If you need more control over the streams, you can use StreamController instead of relying only on Bloc.

  3. Don’t Overcomplicate Simple Apps: BLoC is awesome for large, complex apps, but for simple apps, it might add unnecessary complexity.


Conclusion

BLoC is an incredibly powerful tool for managing state in Flutter apps. By separating the business logic from the UI, it allows you to keep your code clean, scalable, and easier to maintain. With the simple steps and best practices covered in this blog, you should be well on your way to building robust, state-managed apps using BLoC.

Keep your code modular, testable, and scalable.

And as always, happy coding!

0
Subscribe to my newsletter

Read articles from Prashant Bale directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Prashant Bale
Prashant Bale

With 17+ years in software development and 14+ years specializing in Android app architecture and development, I am a seasoned Lead Android Developer. My comprehensive knowledge spans all phases of mobile application development, particularly within the banking domain. I excel at transforming business needs into secure, user-friendly solutions known for their scalability and durability. As a proven leader and Mobile Architect.