Reputation: 1815
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
}
Upvotes: 6
Views: 7516
Reputation: 22832
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
.
Upvotes: 4