Hi all, I have a weird behaviour regarding corouti...
# coroutines
s
Hi all, I have a weird behaviour regarding coroutines on Android and I hope that you can help me: I have a suspending function that performs some UI logic
Copy code
suspend fun showSnackbar() {
    val snackbar = Snackbar.make(...)
    snackbar.show()
    delay(4000)
    snackbar.setAction("...", null) // crash here
    snackbar.dismiss()
}
The functions is called via
launch(Dispatchers.Main) { showSnackbar() }
. Please don't ask why I have to
null
the action. It is required to prevent a false positive memory leak warning. Anyway, the call
snackbar.setAction()
crashes the app with
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
and I wonder if
delay()
might change the Dispatcher?? 🤔 Thanks for your help!
Coroutines version 1.4.3 Kotlin 1.4.31
Wrapping the code after
delay()
in a
withContext(Dispatchers.Main)
fixes the crash but I did not expect this behaviour 😕
u
Pretty sure no dispatcher is changed here. Feel free to throw some logging in there:
Copy code
Log.d("Thread", "On Thread ${Thread.currentThread}")
If you really end up on another thread that would be a bug. But with such a bug, none of my coroutines based code would work
There might be something going wrong in the code you abstracted away for the example?
s
This is pretty much all there is of the code. I just abstracted away building the snackbar UI 🤷🏼‍♂️
u
Did you throw in some logging? You could also add:
Copy code
Log.d("Thread", "My Dispatcher is ${coroutineContext[CoroutineDispatcher]}")
l
This has nothing to do with coroutines, it's an Android restriction that you can only fix by running the UI code interacting with Views APIs on the main thread.
s
I'm aware of this restriction 😉 I run this suspending function like this:
launch(Dispatchers.Main) { showSnackbar() }
u
@louiscad @svenjacobs writes: The functions is called via 
launch(Dispatchers.Main) { showSnackbar() }
s
@uli I added logging right after
delay()
and this is the output:
Copy code
Thread: Thread[DefaultDispatcher-worker-2,5,main]
Dispatcher: Dispatchers.Main
but the next UI code definitely crashes.
u
do you have a stack trace?
l
It looks like the thread is one of
Dispatchers.Default
while the
coroutineContext
has
Dispatchers.Main
🤔
s
Hm, okay, I will check the "backend" code which calls the UI function. Maybe something weird is going on there.
l
To be sure, instead of wrapping the call to
showSnackbar()
, I'd wrap the body of the function itself with
Dispatchers.Main { … }
s
You mean
withContext(Dispatchers.Main) { ... }
?
l
Both work, there's an
operator fun invoke
for
CoroutineDispatcher
.
👍 2
👍🏼 1
s
Okay, sorry for the confusion but it actually was a problem of the backend code. It's a bit more complex. There are cascading coroutine calls and I was of the impression that
launch
inherits the dispatcher... It looked like this
Copy code
launch(Dispatchers.Main) { someFunc() }
and in
someFunc
Copy code
...
launch { showSnackbar() }
...
However adding
Dispatchers.Main
to the second
launch
fixes this.
u
;-)
good you found it
s
Thanks for your help, Uli & Louis!
However, still "interesting" that the UI code before the
delay
did not crash 🤷🏼‍♂️ Maybe just coincidence?
u
But it did say
Dispatchers.Main
while being on a default dispatcher’s thread?
Copy code
Thread: Thread[DefaultDispatcher-worker-2,5,main]
Dispatcher: Dispatchers.Main
s
Yes, after fixing the second
launch
it now says
Copy code
Thread[main,5,main]
u
I am more concerned as to why thread and dispatcher did not match
s
Maybe because of
Copy code
launch(Dispatchers.Main) {
  launch { ... }
}
?
l
@svenjacobs Not all UI calls check for the main thread on Android, that's why it didn't crash earlier.
👍🏼 1