Observing LiveData in ViewModel best practices
Asked Answered
M

1

6

I'm seeking the best way to observe data in ViewModel.

I'm using MVVM + DataBinding.

Repository:

private val data = MutableLiveData<String>()

suspend fun getData(): LiveData<String> {
        return withContext(IO) {
            val response = apiRequest { api.getData() }
            data.postValue(response)
            data
        }
    }

It requests data from server and returns a live data. ViewModel must observe the data changes.

ViewModel:

    suspend fun getData() {
        val data = repository.getData()
        MediatorLiveData<String>().apply {
            addSource(data) {
                gotData(it)
                removeSource(data)
            }
            observeForever { }
        }
    }

    private fun gotData(data: String) {
        //use data
    }

ViewModel uses a MediatorLiveData to observe changes of the LiveData that comes from repository. I've added the data as a source to observe changes and remove it after it triggers to prevent firing events multiple times when I get data multiple times. And there must be a fake observer to MediatorLiveData so the onChange method of the MediatorLiveData triggers.

Let's say that I just need the data to hide/show a view (or even fill data to my recyclerview's adaper). Then I just call the below code and use an Observable and DataBinding like this:

val adapter: ObservableField<DataAdapter> = ObservableField()
val recyclerviewVisibility: ObservableField<Int> = ObservableField(View.GONE)
...
...
recyclerviewVisibility.set(View.VISIBLE)
adapter.set(DataAdapter(dataList))

So I don't need to pass the data to Fragment or Activity to use the viewLifecycleOwner. I also cannot use observeForever in ViewModel because it may fire onChange method multiple times in some situations.

Is there any better approach to get and observe data in ViewModel?


Solution :

I've found out that the best way to reach my goal is to get the data from repository without using LiveData:

Repository

suspend fun getData() : String{
    return  apiRequest { api.getData() }
}

ViewModel

suspend fun getData(){
   val data = repository.getData()
    gotData(data)
}

fun gotData(data: String) {
    //use data
}

It's much simpler now.


Bonus:

extension:

fun <T : Any> ViewModel.request(request: suspend () -> (T), result: (T) -> (Unit) = {}) {
    viewModelScope.launch {
        result(request())
    }
}

usage:

request({request.getData()}) {
    //use data
}
Monteux answered 8/7, 2020 at 11:31 Comment(0)
Q
4

If I figured the problem right, I think you could use the map transformation on the LiveData.

Maybe it's better to change the repository code like the following:

private val reload = MutableLiveData<Unit>()

val data: LiveData<String> =
    reload.switchMap {
        liveData(IO) {
            emit(apiRequest { api.getData() })
        }
    }

fun reload() {
    reload.postValue(Unit) 
}

Then, in your ViewModel using map transtormation, you can intercept the emmited values:

val data: LiveData<String> = 
    repository.data.map {
        gotData(it)
        it
    }

fun reload() {
    repository.reload() 
}

Using this structure, you will be able to call ViewModel's reload() everytime you need to, leading to fetch fresh data from api and emitting it on the ViewModel's data.

Quarterstaff answered 8/7, 2020 at 11:44 Comment(7)
reload.switchMap shows me 'Unresolved Reference' error. so liveData(IO) and emit also show the same error.Monteux
It's because of lacking dependency. Add implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' in build.gradle.Quarterstaff
Adding the dependency solved the errors but reload.switchMap and inner functions are not triggered.Monteux
Do you set an observer on ViewModel's data?Quarterstaff
Setting the observer inside ViewModel is the main problem. Thats why I used MediatorLiveData, added a data source and removed it in my question. Using your answer I should add data.observeForever{} for all responses in init method of the viewmodel.Monteux
The question is why do you want to do that. The ViewModel's goal is to provide data to the view regarding the view's life-cycles. It doesn't seem right that you want to observe on a LiveData in ViewModel instead of observing it in view.Quarterstaff
Let us continue this discussion in chat.Monteux

© 2022 - 2024 — McMap. All rights reserved.