Understanding Flutter BLoC: The Ultimate Guide to Managing App State


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
, orInitial
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:
User triggers an event (e.g., pressing a button).
The BLoC processes the event.
The BLoC sends a new state (e.g., data loaded, error occurred).
UI listens to the state and updates the screen accordingly.
Benefits of Using BLoC
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.
Better Testability: With BLoC, you can easily write unit tests for your business logic since it is separate from the UI.
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.
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(),
),
);
}
}
GitHub Project Link
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:
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.
Use
flutter_bloc
Package: This package simplifies working with BLoC. It provides utilities likeBlocProvider
,BlocBuilder
, andBlocListener
.Don’t Overuse Streams: BLoC is great for complex state management, but if you have simple states, consider using a simpler solution like
Provider
.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.
Use
Cubit
for Simpler Scenarios: If you don't need the complexity of full BLoC, consider usingCubit
, a simpler version of BLoC.
Tips and Tricks to Master BLoC
Use
BlocConsumer
for Side Effects: If you need to perform side effects like showing a dialog or navigating to a new page, useBlocConsumer
instead ofBlocBuilder
.StreamController for More Control: If you need more control over the streams, you can use
StreamController
instead of relying only onBloc
.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!
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.