Skip to content

✨ Proposal: Add BroadcastResultContract API (like ActivityResultContract but for broadcasts) #149

Description

@VahidGarousi

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions