Using coroutines with live data and life scope

You can use the extended livedata classes to work with Kotlin coroutines. Add following dependencies to your module:

dependencies {
    // for ViewModelScope class
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
 
    // for LifecycleScope class
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.2.0")
    
    // for LiveData class
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
}

ViewModelScope

A ViewModelScope is defined for each ViewModel in your app. Any coroutine launched in this scope is automatically canceled if the ViewModel is cleared. Coroutines are useful here for when you have work that needs to be done only if the ViewModel is active.

Code is executed in main thread, but within it you can use other dispatchers.

Launch coroutine in viewModel
class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            
            doSomethingOnUI()
    
            // execute code on other dispatcher
            val result = withContext(Dispatchers.IO) {
               dataRepository.loadData()
            }
        }
    }
}

LifecycleScope

A LifecycleScope is defined for each Lifecycle object. Any coroutine launched in this scope is canceled when the Lifecycle is destroyed. You can access the CoroutineScope of the Lifecycle either via lifecycle.coroutineScope or lifecycleOwner.lifecycleScope properties.

Launch coroutine in fragment
class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        viewLifecycleOwner.lifecycleScope.launch {
            val params = TextViewCompat.getTextMetricsParams(textView)
            val precomputedText = withContext(Dispatchers.Default) {
                PrecomputedTextCompat.create(longTextContent, params)
            }
            TextViewCompat.setPrecomputedText(textView, precomputedText)
        }
    }
}
class MyFragment: Fragment { init { lifecycleScope.launch { whenStarted { // will run only when Lifecycle is at least STARTED loadingView.visibility = View.VISIBLE val canAccess = withContext(Dispatchers.IO) { checkUserAccess() } // When checkUserAccess returns, the next line is automatically // suspended if the Lifecycle is not *at least* STARTED. // We could safely run fragment transactions because we know the // code won't run unless the lifecycle is at least STARTED. loadingView.visibility = View.GONE if (canAccess == false) { findNavController().popBackStack() } else { showContent() } } // This line runs only after the whenStarted block above has completed. } } }
class MyFragment: Fragment { init { lifecycleScope.launchWhenStarted { try { // Call some suspend functions. } finally { // This line might execute after Lifecycle is DESTROYED. if (lifecycle.state >= STARTED) { // Here, since we've checked, it is safe to run any // Fragment transactions. } } } } }
Note. Keep in mind that if the activity restarts, the coroutine is not restarted.

LiveData

When using LiveData, you might need to calculate values asynchronously. For example, you might want to retrieve a user's preferences and serve them to your UI. In these cases, you can use the liveData builder function to call a suspend function, serving the result as a LiveData object.

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}
val user: LiveData<Result> = liveData { emit(Result.loading()) try { emit(Result.success(fetchUser())) } catch(ioException: Exception) { emit(Result.error(ioException)) } }
class MyViewModel: ViewModel() { private val userId: LiveData<String> = MutableLiveData() val user = userId.switchMap { id -> liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { emit(database.loadUserById(id)) } } }
class UserDao: Dao { @Query("SELECT * FROM User WHERE id = :id") fun getUser(id: String): LiveData<User> } class MyRepository { fun getUser(id: String) = liveData<User> { val disposable = emitSource( userDao.getUser(id).map { Result.loading(it) } ) try { val user = webservice.fetchUser(id) // Stop the previous emission to avoid dispatching the updated user // as `loading`. disposable.dispose() // Update the database. userDao.insert(user) // Re-establish the emission with success type. emitSource( userDao.getUser(id).map { Result.success(it) } ) } catch(exception: IOException) { // Any call to `emit` disposes the previous one automatically so we don't // need to dispose it here as we didn't get an updated value. emitSource( userDao.getUser(id).map { Result.error(exception, it) } ) } } }