I should blog more often about things I learn about.
In a native Android app written in Kotlin, I have a LifecycleService class with below code to send a broadcast:
1 2 3 4 5 |
val eventIntent = Intent(CUSTOM_EVENT_ACTION_NAME).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP } applicationContext.sendBroadcast(eventIntent) Log.d("AppTag", "Broadcast sent!") |
Based on the logcat, the broadcast is being sent.
However, below BroadcastReceiver in an Activity doesn’t receive the broadcast, even while it is visible:
1 2 3 4 5 6 7 8 9 10 |
private val customEventReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { Log.d("AppTag", "Custom event receiver triggered") if (intent?.action == GlobalConstants.CUSTOM_EVENT_ACTION_NAME) { Log.d("AppTag", "Confirmed action match, proceeding with task.") performSpecificTask() } } } |
The above BroadcastReceiver is registered from the Activity’s onCreate function, like so:
1 2 3 4 5 6 7 8 9 |
val filter = IntentFilter(GlobalConstants.CUSTOM_EVENT_ACTION_NAME) ContextCompat.registerReceiver( this, customEventReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED ) |
Attempt #1: LocalBroadcastManager – it worked, but…
After browsing the web for potential solutions I came across LocalBroadcastManager. The broadcast was successfully received by the receiver after I changed the sendBroadcast’s applicationContext
and the registerBroadcast’s ContextCompat into LocalBroadcastManager.getInstance(Context).
That is, LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(eventIntent)
and LocalBroadcastManager.getInstance(this).registerReceiver(customEventReceiver, filter)
.
The problem with this approach is that LocalBroadcastManager has been deprecated:
Important changes since 1.0.0
https://developer.android.com/jetpack/androidx/releases/localbroadcastmanager
androidx.localbroadcastmanager
has been fully deprecated. There will be no further releases of this library. Developers should replace usages ofLocalBroadcastManager
with other implementations of the observable pattern. Depending on the use case, suitable options may beLiveData
or reactive streams.
Ideally, the solution should not be a deprecated one.
As to why LocalBroadcastManager made my receiver work, I think it’s because it “bridged” 2 components that weren’t really supposed to be able to communicate. Because from the Android docs:
LocalBroadcastManager is an application-wide event bus and embraces layer violations in your app: any component may listen events from any other.
Why it works, and why it’s been deprecated, apparently have the same explanation. 😅
Attempt #2: Use applicationContext on the sender and receiver — Nope!
I reverted my code to the original, and just made sure they all used applicationContext, matching the context used in the service’s broadcast sender applicationContext.sendBroadcast(eventIntent)
.
So that my registration became:
1 2 3 4 5 6 7 8 9 10 |
val filter = IntentFilter(GlobalConstants.CUSTOM_EVENT_ACTION_NAME) ContextCompat.registerReceiver( applicationContext, customEventReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED ) And the code to unregister became: |
1 2 |
applicationContext.unregisterReceiver(customEventReceiver) |
Hmm… Nope! that didn’t let my receiver get the broadcast.
Attempt #3: Make it an explicit broadcast — YES, IT WORKS!
I reverted my code again to try another approach, which is to make the broadcast an explicit one.
So, in my service, I simply set the package name as shown in code below:
1 2 3 |
val eventIntent = Intent(CUSTOM_EVENT_ACTION_NAME).apply { setPackage(applicationContext.packageName) //... |
And with this 1 line changed in code, my BroadcastReceiver now receives the broadcast! Plus, there’s no deprecated class used here so we prevented 1 new technical debt. :3
Yeah, I think I’ll stick to the 3rd attempt’s approach!
Why does this approach work, though? It’s because starting from Android API level 26, there’s been restriction on implicit broadcasts.
As part of the Android 8.0 (API level 26)Â background execution limits, apps that target the API level 26 or higher can’t register broadcast receivers for implicit broadcasts in their manifest unless the broadcast is sent specifically to them. However, several broadcasts are exempted from these limitations.Â
https://developer.android.com/develop/background-work/background-tasks/broadcasts/broadcast-exceptions
My original code’s broadcast Intent was an implicit one, but specifying a package name converted it into an explicit one, consequently lifting the limitation applied to implicit broadcasts!