I want a button to execute viewmodel's method before navigating to another fragment, but it seems that it is not waiting for all suspending functions to finish before changing screen:
View.class
nextButton.setOnClickListener {
val action = FragmentDirections.AToB()
lifecycleScope.launch {
getViewModel().doSomething()
findNavController().safeNavigate(action)
}
}
ViewModel.class
suspend fun doSomething() {
viewModelScope.launch { //tested with Context.IO and without Context
// long running suspend fun, or even delay(10000)
// screen is navigating before previous instruction finishes
}
}
The use of viewModelScope.launch
initiates separate coroutines, potentially causing the navigation to a new screen before the ongoing operation is completed. The recommended approach is to utilize LiveData
for observing the operation's completion before initiating navigation.
private val _operationComplete = MutableLiveData<Boolean>()
val operationComplete: LiveData<Boolean>
get() = _operationComplete
suspend fun doSomething() {
viewModelScope.launch {
// Long running suspend fun or delay(10000)
_operationComplete.value = true
}
}
In scenarios involving minor operations with assured timely completion, utilizing join
could be considered, provided it ensures prevention of ANR issues.
suspend fun doSomething() {
val job = viewModelScope.launch {
// Long running suspend fun or delay(10000)
}
job.join() // Wait for the coroutine to complete
// Now navigate to the other fragment
}
You can also create a custom coroutine scope and use it to launch your suspending function. This way, you can control the behavior more explicitly.
private val customScope = CoroutineScope(Dispatchers.Default)
suspend fun doSomething() {
customScope.launch {
// Long running suspend fun or delay(10000)
}.join()
}
In your situation I would suggest go with first approach. Happy coding :)