简体   繁体   中英

Observing LiveData in ViewModel best practices

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
}

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM