ARTICLE AD BOX
GlobalScope is indeed the culprit here. It uses the Default dispatcher under the hood, but everything UI related must run on the Main dispatcher.
The reason for that is that the entire UI is single-threaded (albeit massively asynchronous) to guarantee that no race conditions can occur: One thread changing some state while another thread reads that state at the same time. This thread is usually the main thread, the only thread that the Main dispatcher provides.
In your case that means that listState.scrollToItem(it) must be moved to the Main dispatcher. You could do that by simply passing it as a parameter to launch:
GlobalScope.launch(Dispatchers.Main) { listState.scrollToItem(50) // <- LOOK HERE }But as you already figured out, you shouldn't even use GlobalScope at all. It would be slightly better to use CoroutineScope(Dispatchers.Main) instead, but this also creates a dangling coroutine that you have no control over.
The proper solution is to either move this code to somewhere where you naturally have a coroutine scope available, like in a ViewModel. There you can use the viewModelScope that already uses the Main dispatcher, so you wouldn't even need to specify that afterwards.
In all other cases your class or your function where you placed your code should accept a parameter of type CoroutineScope. Then you don't have to create one ad-hoc when you actually want to launch a new coroutine, but you do need to pass one to create the class respectively call the function in the first place. How this is best solved depends on the overall architecture of your app. For classes a common solution would be to use a dependency injection framework like Hilt or Koin to inject the CoroutineScope if you cannot simply pass a fitting CoroutineScope down (like from the ViewModel), for functions you usually declare the scope as a receiver:
fun CoroutineScope.myFunctionThatLaunchesCoroutines() { launch(Dispatchers.Main) { // ... } }myFunctionThatLaunchesCoroutines can now be simply be called from any suspend function without further ado.
In the specific case of performing suspending tasks in a callback you might want to have a look at callbackFlow. That would probably be the cleanest solution when using a layered app architecture: It allows you to use a CoroutineScope from where the Flow is collected (usually the UI, where you have easy access to a matching CoroutineScope).
