✨ Proposal: Add BroadcastResultContract API (like ActivityResultContract but for broadcasts)
Motivation
Currently, Android’s ActivityResultContract pattern provides a clean, declarative way to handle activity results with type safety and lifecycle awareness.
However, there is no equivalent pattern for broadcast-based APIs (e.g., airplane mode changes, connectivity changes, SMS Retriever, or custom in-app broadcasts). Developers must still manually register/unregister BroadcastReceivers, which is verbose, error-prone, and not lifecycle-aware by default.
This proposal introduces a BroadcastResultContract API that mirrors the ActivityResultContract pattern, making broadcast-based workflows consistent, composable, and safer to use.
Proposed API
Base Contract
abstract class BroadcastResultContract<I, O> {
abstract fun createIntentFilter(input: I): IntentFilter
abstract fun parseResult(context: Context, intent: Intent): O?
}
This defines how:
createIntentFilter(input: I) specifies which broadcast(s) to listen for.
parseResult(context, intent) extracts a typed result from a received broadcast.
Example: Airplane Mode Change Contract
sealed class AirplaneModeEvent {
object Enabled : AirplaneModeEvent()
object Disabled : AirplaneModeEvent()
}
class AirplaneModeBroadcastContract :
BroadcastResultContract<Unit, AirplaneModeEvent>() {
override fun createIntentFilter(input: Unit): IntentFilter =
IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)
override fun parseResult(context: Context, intent: Intent): AirplaneModeEvent? {
if (intent.action != Intent.ACTION_AIRPLANE_MODE_CHANGED) return null
val state = intent.getBooleanExtra("state", false)
return if (state) AirplaneModeEvent.Enabled else AirplaneModeEvent.Disabled
}
}
Launcher
interface BroadcastResultLauncher<I> {
fun launch(input: I)
fun cancel()
}
Implementation ties broadcast registration/unregistration to the launcher lifecycle:
private class BroadcastResultLauncherImpl<I, O>(
private val context: Context,
private val contract: BroadcastResultContract<I, O>,
private val singleShot: Boolean,
private val exportedOn33Plus: Boolean,
private val onResult: (O) -> Unit,
) : BroadcastResultLauncher<I> {
...
}
Compose Integration
@Composable
fun <I, O> rememberBroadcastResultLauncher(
contract: BroadcastResultContract<I, O>,
singleShot: Boolean = true,
exportedOn33Plus: Boolean = false,
onResult: (O) -> Unit,
): BroadcastResultLauncher<I>
This mirrors rememberLauncherForActivityResult, ensuring:
- Lifecycle-safe registration/unregistration
- Latest callback references
- Automatic cleanup with
DisposableEffect
Example Usage (in Compose)
@Composable
fun AirplaneModeObserver() {
val launcher = rememberBroadcastResultLauncher(
contract = AirplaneModeBroadcastContract(),
singleShot = false // keep listening for changes
) { event ->
when (event) {
AirplaneModeEvent.Enabled -> {
println("Airplane Mode is ON")
}
AirplaneModeEvent.Disabled -> {
println("Airplane Mode is OFF")
}
}
}
LaunchedEffect(Unit) {
// Start listening for broadcasts
launcher.launch(Unit)
}
}
Benefits
- Declarative & Type-safe: Contracts clearly define inputs/outputs.
- Lifecycle-aware: No manual register/unregister boilerplate.
- Consistent API: Mirrors
ActivityResultContract pattern.
- Composable: Works seamlessly with Jetpack Compose.
Next Steps
👉 Proposal: Introduce this new BroadcastResultContract API into the framework as a first-class feature, starting with Airplane Mode support.
✨ Proposal: Add
BroadcastResultContractAPI (likeActivityResultContractbut for broadcasts)Motivation
Currently, Android’s
ActivityResultContractpattern provides a clean, declarative way to handle activity results with type safety and lifecycle awareness.However, there is no equivalent pattern for broadcast-based APIs (e.g., airplane mode changes, connectivity changes, SMS Retriever, or custom in-app broadcasts). Developers must still manually register/unregister
BroadcastReceivers, which is verbose, error-prone, and not lifecycle-aware by default.This proposal introduces a
BroadcastResultContractAPI that mirrors theActivityResultContractpattern, making broadcast-based workflows consistent, composable, and safer to use.Proposed API
Base Contract
This defines how:
createIntentFilter(input: I)specifies which broadcast(s) to listen for.parseResult(context, intent)extracts a typed result from a received broadcast.Example: Airplane Mode Change Contract
Launcher
Implementation ties broadcast registration/unregistration to the launcher lifecycle:
Compose Integration
This mirrors
rememberLauncherForActivityResult, ensuring:DisposableEffectExample Usage (in Compose)
Benefits
ActivityResultContractpattern.Next Steps
👉 Proposal: Introduce this new BroadcastResultContract API into the framework as a first-class feature, starting with Airplane Mode support.