Search code examples
androidkotlinandroid-livedataandroid-viewmodelkotlin-coroutines

Problem in using viewModelScope with LiveData


I am using viewModelScope in the ViewModel which calls a suspend function in the repository as shown below:

ViewModel

class DeepFilterViewModel(val repo: DeepFilterRepository) : ViewModel() {

var deepFilterLiveData: LiveData<Result>? = null

 fun onImageCompressed(compressedImage: File): LiveData<Result>? {
    if (deepFilterLiveData == null) {
        viewModelScope.launch {
            deepFilterLiveData =  repo.applyFilter(compressedImage)
        }

    }
    return deepFilterLiveData
 }
}

Repository

class DeepFilterRepository {

suspend fun applyFilter(compressedImage: File): LiveData<Result> {
    val mutableLiveData = MutableLiveData<Result>()
    mutableLiveData.value = Result.Loading

    withContext(Dispatchers.IO) {
        mutableLiveData.value = Result.Success("Done")

    }
    return mutableLiveData
 }
}

I am observing the LiveData from the Fragment as shown below:

 viewModel.onImageCompressed(compressedImage)?.observe(this, Observer { result ->
        when (result) {

            is Result.Loading -> {
                loader.makeVisible()
            }

            is Result.Success<*> -> {
                // Process result

            }
        }
    })

The problem is I am getting no value from the LiveData. If I don't use viewModelScope.launch {} as shown below, then everything works fine.

class DeepFilterViewModel(val repo: DeepFilterRepository) : ViewModel() {

var deepFilterLiveData: LiveData<Result>? = null

 fun onImageCompressed(compressedImage: File): LiveData<Result>? {
    if (deepFilterLiveData == null) {
            deepFilterLiveData =  repo.applyFilter(compressedImage)
    }
    return deepFilterLiveData
 }
}

I don't know what I am missing. Any help will be appreciated.


Solution

  • This code:

    viewModelScope.launch {
       deepFilterLiveData =  repo.applyFilter(compressedImage)
    }
    

    returns immediately so when you first invoke the onImageCompressed() method you return null as deepFilterLiveData. Because in your UI you use ?. on the null return value of onImageCompressed() the when clause will not be reached. The code without the coroutine works because in that case you have sequential code, your ViewModel awaits for the repository call.

    To solve this you could keep the LiveData for the ViewModel-UI interaction and return the values directly from the repository method:

    class DeepFilterRepository {
    
        suspend fun applyFilter(compressedImage: File) = withContext(Dispatchers.IO) {
            Result.Success("Done")
        }
    
    }
    

    And the ViewModel:

    class DeepFilterViewModel(val repo: DeepFilterRepository) : ViewModel() {
    
        private val _backingLiveData = MutableLiveData<Result>()
        val deepFilterLiveData: LiveData<Result>
           get() = _backingLiveData
    
        fun onImageCompressed(compressedImage: File) {
            // you could also set Loading as the initial state for _backingLiveData.value           
           _backingLiveData.value = Result.Loading
            viewModelScope.launch {
                _backingLiveData.value = repo.applyFilter(compressedImage)
            }
        }     
    }