'Avoid UI Blocking while running an operation that requires to be run on the main thread
I use an API that has a method, let's say run() that must be executed on the main thread, or else it'll throw an exception. Now, I've tried coroutines, and it doesn't work in a standard launch{...} block, which is understandable. Now, since it is a little long running task, I wish to show a UI to the user, indicating the same, i.e., a process is taking place. Now, I do not require assistance on the animation logic, but I cannot understand how is the animation supposed to keep up alongside all the heavy IO stuff that may be going on on the main thread.
Also, I've been experiencing some very odd behaviour in this Composable. Kingly have a look,
@Composable
fun CustomC() {
var trigger by remember {
mutableStateOf(false)
}
val color by animateColorAsState(targetValue = if (trigger) Color.Gray else Color.Cyan)
Surface(Modifier.fillMaxSize()) {
Text(
modifier = Modifier.background(color),
text = "Running"
)
}
// I tried this but this seems to produce a crash, indicating that the run method is not running on the main thread, but how? Removing this LaunchedEffect removes the error.
// LaunchedEffect(Unit){
// delay(2000)
// trigger = true
// }
run() // Must be executed on the Main Thread
}
This app crashes if I put that LaunchedEffect block over there, but it is not even interacting with run() in any way, per my knowledge. And another strange behaviour is as follows:
@Composable
fun CustomC() {
var trigger by remember {
mutableStateOf(false)
}
val color by animateColorAsState(targetValue = if (trigger) Color.Gray else Color.Cyan)
Surface(Modifier.fillMaxSize()) {
Text(
modifier = Modifier.background(color),
text = "Running"
)
}
trigger = true
run() // Must be executed on the Main Thread
}
Now, you would expect the Composable to be turning Cyan before the run method is called, right? IT DOESN'T!! IT JUST DOESN'TTTT
It just starts executing run() and then finally AFTER the method is done executing, the Composable turns cyan. This clearly implies that the recompositions are blocked while the method is running, so all I need is a way to get around that.
EDIT: An important piece of information that I missed earlier, when I call the run() method inside of a LaunchedEffect, the method seems to work fine, i.e., the app doesn't crash, but the UI is still blocked.
Also, if I call the method inside a launch block WITHIN a LaunchedEffect, the same thing happens as above, where the method runs fine but the UI is clogged. What is the role of launch here then?
FINAL EDIT: A very rare thing I saw in this scenario was when the crash appeared, it did not throw any sort of an exception. It just raised an error like so:
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x2f6000412f6018 in tid 29693
I got this error from my search history, and it pointed me to some things related to programming in the NDK, which I do not. Also, now no matter what I do, the error won't come up. Seems like a glitch in Android or Studio.
Solution 1:[1]
Since the cause of the crash seems to be the method was not given access to I/O by the system, the problem was resolved using a coroutine with I/O access.
Now, in my ViewModel, I wrapped the run method in a coroutine like this
suspend fun runWithIO(){ // custom method
withContext(Dispatchers.IO){
run() // The original method, provided by the API
}
}
This satisfies all the constraints, whatsoever - Runs on a thread with I/O access, does not block the Main Thread, uses best practices like coroutines without any side-effects.
So, the explanation was posted by Johann in the first comment on the original post which has just been adapted to my specific use-case here.
So, if an API that you might be using states that the methods are supposed to be run only on the main thread, you should probably play around for a while with different coroutine contexts, since the API may only require the main thread to perform I/O operations, which can also be performed in a simple coroutine like this. If the method does indeed require the main thread, there's also Dispatchers.Main to assist you with that, you can run only the required part of a function on the Main Thread, but please note, this call WILL BE blocking.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
