flutter_bloc widgets and rebuild scope
flutter_bloc provides a set of widgets that bind a bloc to the UI. An important note from the docs: all of them work with both `Cubit` and `Bloc` — so moving from Cubit to Bloc leaves the UI code unchanged.
BlocProvider— places a bloc in the tree and manages its lifetime;MultiBlocProviderflattens the nesting.BlocBuilder— builds UI from state; thebuildWhencondition filters which changes cause a rebuild.BlocSelector— selects a slice of state; if the selected value is unchanged, no rebuild happens. The docs' condition: the selected value must be immutable, otherwise the comparison cannot work.BlocListener— a side effect without a rebuild: a snackbar, dialog, navigation; filtered withlistenWhen.BlocConsumer— builder and listener combined.RepositoryProvider— for stateless objects such as repositories and services.
| Need | Widget | Note |
|---|---|---|
| Build UI from state | `BlocBuilder` | The rebuild is confined to the builder's contents |
| Track a single field | `BlocSelector` | The selected value must be immutable |
| A snackbar, dialog, navigation | `BlocListener` | No rebuild; filtered with `listenWhen` |
| Both at once | `BlocConsumer` | `buildWhen` and `listenWhen` are given separately |
| Add an event from a callback | `context.read<T>()` | Functionally equal to `BlocProvider.of<T>(context)`; it does not listen |
| Provide a repository/service | `RepositoryProvider` | No state notifications — plain DI |
dart
class TodoPage extends StatelessWidget {
const TodoPage({super.key});
@override
Widget build(BuildContext context) {
return BlocListener<TodoBloc, TodoState>(
// Yalnız xəta yeni yarandıqda işləyir.
listenWhen: (previous, current) => previous.error != current.error && current.error != null,
listener: (context, state) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.error!)),
);
},
child: Scaffold(
appBar: AppBar(
// Yalnız sayğac dəyişdikdə bu hissə rebuild olunur.
title: BlocSelector<TodoBloc, TodoState, int>(
selector: (state) => state.todos.length,
builder: (context, count) => Text('Tapşırıqlar ($count)'),
),
),
body: BlocBuilder<TodoBloc, TodoState>(
// Status dəyişməyibsə siyahını yenidən qurmuruq.
buildWhen: (previous, current) =>
previous.status != current.status || previous.todos != current.todos,
builder: (context, state) => switch (state.status) {
TodoStatus.loading => const TodoSkeleton(),
TodoStatus.failure => const ErrorView(),
TodoStatus.success => TodoListView(todos: state.todos),
TodoStatus.initial => const SizedBox.shrink(),
},
),
floatingActionButton: FloatingActionButton(
// read: dinləmir, callback üçün düzgün seçimdir.
onPressed: () => context.read<TodoBloc>().add(TodoAdded('Yeni')),
child: const Icon(Icons.add),
),
),
);
}
}Controlling the rebuild scope precisely.
Direct instructions from the docs:
- ✅ Use
context.readto add events in callbacks. - ❌ Avoid using
context.readto obtain state inbuild— in the docs' words this is error prone, because the widget will not rebuild when the state changes. context.watch<T>()is functionally equivalent toBlocProvider.of<T>(context, listen: true)and is only accessible inside thebuildmethod of aStatelessWidgetor aStateclass.- ✅ Prefer
BlocBuilderovercontext.watchto scope rebuilds explicitly (or narrow the scope with aBuilder).
📚 Sources and documentation
- Flutter Bloc conceptsofficialbloclibrary.dev
The official source for every widget, buildWhen/listenWhen and the context.read/watch/select rules.
- package:flutter_blocofficialpub.dev
- package:provider (source of the extensions)officialpub.dev
flutter_bloc re-exports the context.read/watch/select extensions from this package.