Broadcast receivers

Lifecycle and app components Mid-level Kotlin 2.0+ API 26+ Reviewed By Best Latest Apps

Pick the fix

Registering a runtime receiver crashes on Android 14 with a SecurityException. What's the fix?

Options
Hint 1 of 1

What did Android 14 start requiring for registerReceiver?

Choose an answer, then open Show answer below to check it.

Show answer

Answer: A Pass ContextCompat.RECEIVER_NOT_EXPORTED (or EXPORTED if other apps must send to it)

Correct. Android 14 requires an explicit export flag for runtime receivers.

Why not the others?
  • B Move it to the manifest. Implicit broadcasts mostly can't be received there.
  • C Add android:exported to the Activity. The flag belongs to the receiver registration.
  • D Use LocalBroadcastManager. It's deprecated, and only covers in-app broadcasts.

30-second answer

A BroadcastReceiver reacts to system or app events. Since Android 8, most implicit broadcasts can't be received by receivers declared in the manifest; register them at runtime while your app runs instead. Since Android 14, runtime receivers must say whether they're exported with RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED, unless they only receive system broadcasts. onReceive runs on the main thread and must finish quickly.

Practise saying it · 35 s. No recording, just a timer

Explain broadcast receivers today.

Say your answer out loud, then check which of these points you covered:

  • Implicit manifest receivers mostly blocked
  • Register at runtime with an export flag
  • onReceive is short and on main
  • Use WorkManager for longer work

Deep dive

A few broadcasts are still allowed in the manifest, such as BOOT_COMPLETED and LOCALE_CHANGED.

For longer work from onReceive, enqueue WorkManager or use goAsync() for a short extension.

For in-app events, use a Flow or callback; LocalBroadcastManager is deprecated.

Use ContextCompat.registerReceiver with a flag so the same code works on every API level.

A runtime receiver Compile-checked · Kotlin 2.4.21
fun listenForPowerSave(context: Context, onChange: () -> Unit): BroadcastReceiver {
    val receiver = object : BroadcastReceiver() {
        override fun onReceive(c: Context, intent: Intent) = onChange()
    }
    ContextCompat.registerReceiver(
        context, receiver,
        IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED),
        ContextCompat.RECEIVER_NOT_EXPORTED
    )
    return receiver
}

Common mistakes

  • Declaring a manifest receiver for CONNECTIVITY_CHANGE and expecting it to fire.
  • Doing network calls in onReceive.
  • Registering without the exported flag on Android 14.

Senior take & follow-ups

Name the restrictions with their versions and the modern alternatives.

Follow-up questions

Think of your answer first, then open the hint.

  1. How do you react to connectivity changes now?

    Show hint

    ConnectivityManager.registerDefaultNetworkCallback.

  2. What does goAsync give you?

    Show hint

    A short time to finish work off the main thread before the receiver is considered done.

Suggest a fix if something here is wrong or out of date.