Background
I've found some classes/functions on a large app I work on, that have calls that shouldn't be on the UI thread (such as accessing the storage or DB).
Such operations could cause ANRs, and indeed I can see a percentage of ANRs on the Play Console.
I'd like to change this, and hopefully by using Kotlin Coroutines to also have a bit more order in code.
So, currently I work on a class that extends BroadcastReceiver
and so it needs the onReceive
callbacks to be handled one after another on the UI thread, each one will have to "wait" for the previous ones to finish.
Inside the onReceive callback, there are sadly calls that should be done on the background thread, and some on the UI thread. Sometimes there are conditions that have them both.
Meaning for example :
if( someCheckOnUiThread() && someDbOperation()) {
...
}
The problem
I'm quite new to Kotlin Coroutines, and even though I've found how to handle this, I'm pretty sure there is a more official way to do it, as I've read some tips and comments about this from others (here).
What I've tried
What I did actually works, but it looks more like a workaround:
private val mainScope = MainScope()
private val backgroundWorkDispatcher: CoroutineDispatcher =
java.util.concurrent.Executors.newFixedThreadPool(1).asCoroutineDispatcher()
And then use them right in the onReceive
callback:
@UiThread
override fun onReceive(somcContext: Context, intent: Intent) {
val context = somcContext.applicationContext
//use goAsync just because I'm in BroadcastReceiver
val pendingAsyncResult = goAsync()
mainScope.launch {
runInterruptible(backgroundWorkDispatcher) {
// <-- some code here
}
}.invokeOnCompletion { throwable ->
// last operation after done with everything here:
pendingAsyncResult.finish()
}
//done right away here, and yet the handling will be done one after another, freely
}
Inside the runInterruptible
, I can reach the UI thread by calling runBlocking(mainScope.coroutineContext) {}
, and I can also cancel the task using cancel()
even within.
Using runBlocking
is important because I need to wait there for a result. Of course I could use the alternatives when it makes sense, but then I could also use a simple Handler as I don't wait for a result.
I also use backgroundWorkDispatcher
to make sure all background operations will be on a single thread, to wait for next operations, one after another.
The question
What are the alternatives to this solution? Something more elegant and/or shorter? Something more official?
Note that I need to handle the operations that are queued by the UI one-after-another, each waiting for the previous one to finish. The BroadcastReceiver
is only an example. I'm sure there are (sadly) much harder places to fix in code, but I want to know how to properly handle this first.