Bloc Consumer
A composable that combines BlocListener and BlocBuilder with buildWhen into a single, declarative component.
Use BlocConsumer when a state transition needs both a side effect (navigation, banner, sound) and a conditional UI rebuild — each gated by its own independent predicate, driven by a single Flow subscription.
When to use BlocConsumer vs BlocListener + BlocBuilder
| Need | Prefer |
|---|---|
| Side effect only | BlocListener |
| UI rebuild only | BlocBuilder |
| Side effect + controlled rebuild | BlocConsumer |
Usage
// Show a "tier up" animation AND update the tier badge — each gated by the same
// predicate, driven by a single subscription.
BlocConsumer(
bloc = scoreBloc,
listenWhen = { old, new -> tierFor(old) != tierFor(new) },
listener = { _ -> triggerTierAnimation() },
buildWhen = { old, new -> tierFor(old) != tierFor(new) },
) { state ->
TierBadge(tier = tierFor(state))
}Both predicates receive (previous, current) states and are evaluated on the same emission. You can use different predicates for each:
// The side effect fires every 5 pts; the UI rebuilds every 10 pts.
BlocConsumer(
bloc = scoreBloc,
listenWhen = { _, new -> new.score % 5 == 0 },
listener = { _ -> playChime() },
buildWhen = { old, new -> old.score / 10 != new.score / 10 },
) { state ->
TierBadge(tier = tierFor(state.score))
}Content closure
content receives a state snapshot (S), not the live bloc. This ensures buildWhen fully controls when the composable recomposes. To dispatch events from inside content, resolve the bloc directly:
BlocConsumer(myBloc, ...) { state ->
val bloc = remember { BlocRegistry.resolve(MyBloc::class) }
Text("${state}")
Button(onClick = { bloc.add(MyEvent.DoSomething) }) { Text("Go") }
}Default behaviour
Both listenWhen and buildWhen default to null, meaning they always trigger — equivalent to BlocListener and BlocBuilder respectively.
Parameters
The StateEmitter to observe.
Optional predicate (previous, current) -> Boolean. The listener is called only when this returns true. previous is the last emitted state (independent of what was last rendered).
Side-effect callback invoked with the new state. Not called for the initial state.
Optional predicate (previous, current) -> Boolean. The content snapshot is updated — triggering recomposition — only when this returns true. previous is the last displayed state (last state that was rendered).
A composable receiving the last approved state snapshot.
Overload that resolves the bloc from BlocRegistry by KClass.
BlocConsumer(
blocClass = ScoreBloc::class,
listenWhen = { old, new -> tierFor(old) != tierFor(new) },
listener = { _ -> triggerTierAnimation() },
buildWhen = { old, new -> old / 10 != new / 10 },
) { state ->
TierBadge(tier = tierFor(state))
}