Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 363
Mehul Kanzariya Avatar asked May 01 '19 07:05

Mehul Kanzariya


People also ask

Why use flow instead of LiveData?

Flow: Simple things are harder and complex things are easier. LiveData did one thing and it did it well: it exposed data while caching the latest value and understanding Android's lifecycles. Later we learned that it could also start coroutines and create complex transformations, but this was a bit more involved.

Is LiveData lifecycle-aware?

LiveData is lifecycle-aware, following the lifecycle of entities such as activities and fragments. Use LiveData to communicate between these lifecycle owners and other objects with a different lifespan, such as ViewModel objects.


1 Answers

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)
        }
    }     
}
like image 81
user Avatar answered Sep 28 '22 21:09

user