BlocConsumer

fun <B : StateEmitter<S>, S : Any> BlocConsumer(bloc: B, listenWhen: (previous: S, current: S) -> Boolean? = null, listener: (state: S) -> Unit, buildWhen: (previous: S, current: S) -> Boolean? = null, content: @Composable (state: S) -> Unit)

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

NeedPrefer
Side effect onlyBlocListener
UI rebuild onlyBlocBuilder
Side effect + controlled rebuildBlocConsumer

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

bloc

The StateEmitter to observe.

listenWhen

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).

listener

Side-effect callback invoked with the new state. Not called for the initial state.

buildWhen

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).

content

A composable receiving the last approved state snapshot.


fun <B : StateEmitter<S>, S : Any> BlocConsumer(blocClass: KClass<B>, listenWhen: (previous: S, current: S) -> Boolean? = null, listener: (state: S) -> Unit, buildWhen: (previous: S, current: S) -> Boolean? = null, content: @Composable (state: S) -> Unit)

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))
}