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?
- You do something — like tap a button or scroll the page.
- This action creates an Event — a message that says “something happened.”
- The Bloc gets the event and does the work — maybe call an API, check data, or run some calculations.
- When the work is done, the Bloc sends out a new State — a message about what the app should look like now.
- 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 {}
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);
}
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());
}
}
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();
},
)
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)