Sparround

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; MultiBlocProvider flattens the nesting.
  • BlocBuilder — builds UI from state; the buildWhen condition 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 with listenWhen.
  • BlocConsumer — builder and listener combined.
  • RepositoryProvider — for stateless objects such as repositories and services.
NeedWidgetNote
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.read to add events in callbacks.
  • ❌ Avoid using context.read to obtain state in build — in the docs' words this is error prone, because the widget will not rebuild when the state changes.
  • context.watch<T>() is functionally equivalent to BlocProvider.of<T>(context, listen: true) and is only accessible inside the build method of a StatelessWidget or a State class.
  • ✅ Prefer BlocBuilder over context.watch to scope rebuilds explicitly (or narrow the scope with a Builder).

📚 Sources and documentation