flutter
/

BlocSelector: Optimized State Selection in Flutter Bloc

Last Sync: Today

On this page

10
0%
5 min read
Remaining
5 minleft

Click any section to jump — progress syncs automatically

flutter

BlocSelector: Optimized State Selection in Flutter Bloc

When your state objects become complex, rebuilding the entire UI on every state change can hurt performance. BlocSelector solves this by allowing you to listen to only the specific parts of the state that your widget cares about. It rebuilds only when the selected value changes, making your app faster and more efficient.

What is BlocSelector?

BlocSelector<Bloc, State, Selected> is a Flutter widget that rebuilds only when the selected value changes. It takes a selector function that extracts a part of the state, and a builder that receives the selected value. Unlike BlocBuilder, which rebuilds on any state change, BlocSelector gives you fine‑grained control over rebuilds.

When to Use BlocSelector

  • Large state objects – When state has many fields and you only need a few.
  • Frequent state changes – To avoid rebuilding expensive widgets.
  • Performance‑critical screens – Lists, animations, or complex forms.
  • Independent UI components – When widgets depend on different parts of the same state.

Basic Usage

Define a selector function that returns the part of the state you need. The builder receives the selected value and returns a widget. The widget rebuilds only when the selected value changes (based on == equality).

DARTRead-only
1
BlocSelector<UserBloc, UserState, String>(
  selector: (state) => state.user.name,
  builder: (context, name) {
    return Text('Hello, $name');
  },
)

In this example, the Text widget rebuilds only when the user's name changes, not when other fields like email or age change.

Comparing BlocSelector with Other Approaches

DARTRead-only
1
// Rebuilds on ANY state change
BlocBuilder<UserBloc, UserState>(
  builder: (context, state) {
    return Text('Hello, ${state.user.name}');
  },
)
DARTRead-only
1
// Rebuilds only when name changes
BlocSelector<UserBloc, UserState, String>(
  selector: (state) => state.user.name,
  builder: (context, name) {
    return Text('Hello, $name');
  },
)

You can achieve similar behaviour using context.select inside a BlocBuilder or a custom widget, but BlocSelector is the dedicated widget for this purpose and often more readable.

DARTRead-only
1
// Using context.select inside a Builder
Builder(
  builder: (context) {
    final name = context.select((UserBloc bloc) => bloc.state.user.name);
    return Text('Hello, $name');
  },
)

Real-World Example

Consider a Dashboard screen with a complex DashboardState containing user profile, notifications, and settings. Different widgets need different parts of the state.

DARTRead-only
1
class DashboardState extends Equatable {
  final User user;
  final List<Notification> notifications;
  final bool darkMode;
  // ... many other fields

  DashboardState({required this.user, required this.notifications, required this.darkMode});

  @override
  List<Object> get props => [user, notifications, darkMode];
}

// Widget that shows only the user's avatar
BlocSelector<DashboardBloc, DashboardState, String>(
  selector: (state) => state.user.avatarUrl,
  builder: (context, avatarUrl) {
    return CircleAvatar(backgroundImage: NetworkImage(avatarUrl));
  },
)

// Widget that shows only the unread count
BlocSelector<DashboardBloc, DashboardState, int>(
  selector: (state) => state.notifications.where((n) => !n.isRead).length,
  builder: (context, unreadCount) {
    return Badge(label: Text('$unreadCount'), child: Icon(Icons.notifications));
  },
)

// Widget that rebuilds only when darkMode changes
BlocSelector<DashboardBloc, DashboardState, bool>(
  selector: (state) => state.darkMode,
  builder: (context, isDarkMode) {
    return Switch(value: isDarkMode, onChanged: (value) {});
  },
)

BlocSelector with Complex Selections

The selector can return any type, including custom objects. However, ensure those objects are immutable and implement equality (Equatable) to avoid false positives.

DARTRead-only
1
// Select a custom object
BlocSelector<CartBloc, CartState, CartItem?>(
  selector: (state) => state.items.firstWhereOrNull((item) => item.id == productId),
  builder: (context, item) {
    return Text('Quantity: ${item?.quantity ?? 0}');
  },
)

Performance Benefits

  • Fewer rebuilds – Only widgets that depend on changed data rebuild.
  • Better frame rates – Reduces layout and painting work, especially on low‑end devices.
  • Cleaner separation – Each widget explicitly declares its dependencies.
  • Easier debugging – You can trace exactly why a widget rebuilt by checking the selected value.

Best Practices

  • Use BlocSelector for widgets that depend on a small part of the state – Avoid passing the entire state to a widget that only needs one field.
  • Combine with Equatable – Always extend Equatable on your state classes to enable correct equality checks.
  • Keep selectors simple – If a selector becomes complex, move it to a method or a getter on the state class.
  • Use BlocSelector inside ListView.builder items – Prevents rebuilding the entire list when one item's data changes.
  • Prefer BlocSelector over manual context.select inside build – It's more declarative and easier to read.

Common Mistakes

  • ❌ Not using Equatable – The selector's return value will be compared by reference, causing unnecessary rebuilds even when data is the same.
  • ❌ Selecting a mutable object – If the selected object is mutated (not replaced), equality won't detect changes. Always use immutable state.
  • ❌ Selecting the entire state – That defeats the purpose; use BlocBuilder instead if you need the whole state.
  • ❌ Over‑optimizing prematurely – For small states, BlocBuilder is fine; use BlocSelector when performance issues appear.
  • ❌ Forgetting to include the selected value in the builder – The builder receives only the selected value, not the full state.

What's Next?

Now that you know how to optimise rebuilds, explore other Bloc widgets like BlocListener for side effects and BlocConsumer for combined builder and listener patterns.

Next, explore BlocListener and BlocConsumer.

Test Your Knowledge

Q1
of 3

What is the primary benefit of using BlocSelector over BlocBuilder?

A
It automatically handles errors
B
It reduces unnecessary widget rebuilds
C
It provides a simpler API
D
It works with multiple blocs
Q2
of 3

Which package is essential for correct equality checks in BlocSelector?

A
bloc_concurrency
B
equatable
C
flutter_bloc
D
provider
Q3
of 3

When should you consider using BlocSelector?

A
For every widget that uses a bloc
B
Only when using Cubit
C
When state objects are large and only small parts are needed
D
When you need to handle side effects

Frequently Asked Questions

What is the difference between BlocSelector and BlocBuilder?

BlocBuilder rebuilds its child on every state change. BlocSelector rebuilds only when the selected part of the state changes. This makes BlocSelector more efficient for complex states where only specific fields are needed.

Can I use BlocSelector with Cubit?

Yes, BlocSelector works with any Bloc or Cubit. The selector receives the current state from the cubit, and the builder rebuilds when the selected value changes.

How does BlocSelector compare values?

It uses the == operator on the selected value. For custom objects, you should implement == and hashCode (or use Equatable) to ensure correct comparison. Primitives like int, String, and bool work out of the box.

Can I select multiple fields?

Yes, you can select a composite object (e.g., a tuple or a custom class containing multiple fields). However, ensure that object implements equality correctly, or it may cause unnecessary rebuilds. Alternatively, use multiple BlocSelector widgets for each field.

Previous

bloc consumer

Next

bloc context read watch

Related Content

Need help?

Explore our comprehensive docs or start a chat with our tech experts.