Bloc / Cubit
BLoC (Business Logic Component) separates UI from state by routing events through a Bloc class that emits state objects. Widgets dispatch events (`context.read
A counter and an async load Bloc
EXAMPLE
// pubspec.yaml -> flutter_bloc: ^8.1.0
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// ---------- Counter Bloc ----------
sealed class CounterEvent {}
class Increment extends CounterEvent {}
class Reset extends CounterEvent {}
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<Increment>((e, emit) => emit(state + 1));
on<Reset>((e, emit) => emit(0));
}
}
// ---------- Async load Bloc with states as a sealed class ----------
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState { final String name; UserLoaded(this.name); }
class UserError extends UserState { final String message; UserError(this.message); }
sealed class UserEvent {}
class LoadUser extends UserEvent { final String id; LoadUser(this.id); }
class UserBloc extends Bloc<UserEvent, UserState> {
UserBloc() : super(UserInitial()) {
on<LoadUser>((e, emit) async {
emit(UserLoading());
try {
await Future.delayed(const Duration(milliseconds: 400));
emit(UserLoaded('Alice (id=${e.id})'));
} catch (err) {
emit(UserError(err.toString()));
}
});
}
}
// ---------- UI ----------
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
home: MultiBlocProvider(
providers: [
BlocProvider(create: (_) => CounterBloc()),
BlocProvider(create: (_) => UserBloc()),
],
child: const HomePage(),
),
);
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bloc demo')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
BlocBuilder<CounterBloc, int>(
builder: (_, n) => Text('Count: $n', style: const TextStyle(fontSize: 24)),
),
const SizedBox(height: 8),
FilledButton(
onPressed: () => context.read<CounterBloc>().add(Increment()),
child: const Text('Increment'),
),
const SizedBox(height: 24),
BlocBuilder<UserBloc, UserState>(
builder: (_, s) => switch (s) {
UserInitial() => const Text('No user loaded'),
UserLoading() => const CircularProgressIndicator(),
UserLoaded(:final name) => Text('Loaded: $name'),
UserError(:final message) => Text('Error: $message'),
},
),
FilledButton(
onPressed: () => context.read<UserBloc>().add(LoadUser('42')),
child: const Text('Load user'),
),
],
),
),
);
}
}
Why it matters
Sealed event/state classes plus the Dart 3 switch expression turn Bloc into something close to a state-machine you can read top to bottom. If a state needs more than three or four fields, split the screen into smaller Blocs rather than letting one accumulate the whole feature.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
class CounterCubit extends Cubit<int> {
CounterCubit() : super(0);
void inc() => emit(state + 1);
}
BlocProvider(
create: (_) => CounterCubit(),
child: BlocBuilder<CounterCubit, int>(
builder: (c, n) => Text('$n'),
),
);
Try it Yourself »
Discussion
Loading…