DEV Community

Masrafi Anam
Masrafi Anam

Posted on

Flutter Bloc Pattern Explained: States & Events for Beginners

Introduction — Why Should You Care?

When flutter apps grow, using setState() everywhere can turn your code into a tangled mess.
Bloc (Business Logic Component) fixes that by separating what happens (events) from what you show (states), keeping your code clean and testable.

How the Bloc works?

  1. You do something — like tap a button or scroll the page.
  2. This action creates an Event — a message that says “something happened.”
  3. The Bloc gets the event and does the work — maybe call an API, check data, or run some calculations.
  4. When the work is done, the Bloc sends out a new State — a message about what the app should look like now.
  5. The UI is always listening, so when the state changes, the screen updates automatically.

Code Example with Bloc
Now we show here full login with bloc. Let’s start,

1️⃣ Events:
Here, we created two events — one for login and one for logout. Remember, events represent actions taken by the user.
// auth_event.dart

abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
 final String email;
 final String password;
LoginRequested(this.email, this.password);
}
class LogoutRequested extends AuthEvent {}
Enter fullscreen mode Exit fullscreen mode

2️⃣ States
Next, we create the states. In this example, we have three: Loading, Loaded, and Error. You can also add your own, like LoadedDone, if your app requires it.
// auth_state.dart

abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class Authenticated extends AuthState {
 final String userId;
 Authenticated(this.userId);
}
class AuthError extends AuthState {
 final String message;
 AuthError(this.message);
}
Enter fullscreen mode Exit fullscreen mode

3️⃣ The Bloc
The Bloc class takes in a user action (event) and outputs the appropriate data (state).
// auth_bloc.dart

import 'package:flutter_bloc/flutter_bloc.dart';
import 'auth_event.dart';
import 'auth_state.dart';
import '../repositories/auth_repository.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
 final AuthRepository authRepository;
AuthBloc(this.authRepository) : super(AuthInitial()) {
 on<LoginRequested>(_onLoginRequested);
 on<LogoutRequested>(_onLogoutRequested);
 }
Future<void> _onLoginRequested(
 LoginRequested event,
 Emitter<AuthState> emit,
 ) async {
 emit(AuthLoading());
 try {
 final userId = await authRepository.login(event.email, event.password);
 emit(Authenticated(userId));
 } catch (e) {
 emit(AuthError(e.toString()));
 }
 }
void _onLogoutRequested(
 LogoutRequested event,
 Emitter<AuthState> emit,
 ) {
 authRepository.logout();
 emit(AuthInitial());
 }
}
Enter fullscreen mode Exit fullscreen mode

4️⃣ Using It in UI
This is the UI section. Here, we take the state returned from the Bloc and show it in a ListView.builder.

BlocProvider(
 create: (context) => AuthBloc(context.read<AuthRepository>()),
 child: LoginScreen(),
);
// Inside LoginScreen:
BlocBuilder<AuthBloc, AuthState>(
 builder: (context, state) {
 if (state is AuthLoading) return CircularProgressIndicator();
 if (state is Authenticated) return Text("Welcome, ${state.userId}");
 if (state is AuthError) return Text("Error: ${state.message}");
 return LoginForm();
 },
)
Enter fullscreen mode Exit fullscreen mode

Why Use Bloc?
Separation of Concerns: Keeps business logic out of the UI, making the app easier to maintain.
Testability: Each layer (Bloc, Event, State) can be tested independently.
Scalability: Well-suited for large apps where managing state becomes complex.
Consistency: Provides a predictable state flow, making debugging easier.

What challenges have you faced with Bloc? Share your experiences or questions in the comments!

Top comments (0)