Search code examples
androidkotlinkotlin-coroutinesandroid-lifecycleandroid-viewmodel

Why lifecycleScope is not waiting for viewmodelscope to finish?


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
    }
}

Solution

  • 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 :)