在ViewModel中观察LiveData的最佳实践

6

我正在寻找最佳方法来观察 ViewModel 中的数据。

我正在使用 MVVM + DataBinding。

仓库:

private val data = MutableLiveData<String>()

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

这段代码从服务器请求数据并返回实时数据。ViewModel 必须观察数据的变化。

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使用MediatorLiveData来观察来自仓库的LiveData的变化。我已将该数据作为源添加以观察更改,并在触发后将其删除,以防多次获取数据时多次触发事件。必须有一个虚拟观察者连接到MediatorLiveData,以便触发MediatorLiveData的onChange方法。

假设我只需要数据来隐藏/显示视图(甚至向我的recyclerview适配器填充数据)。那么,我只需调用下面的代码并像这样使用Observable和DataBinding:

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

所以我不需要将数据传递给Fragment或Activity来使用viewLifecycleOwner。 同时我也不能在ViewModel中使用observeForever,因为它可能会在某些情况下多次触发onChange方法。
是否有更好的方法在ViewModel中获取和观察数据?
解决方案: 我已经发现实现我的目标的最佳方法是从存储库中获取数据而不使用LiveData: Repository
suspend fun getData() : String{
    return  apiRequest { api.getData() }
}

视图模型

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

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

现在这个变得更简单了。


额外奖励:

扩展:

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

用法:

request({request.getData()}) {
    //use data
}
1个回答

4
如果我对问题的理解正确,我认为你可以在LiveData上使用map转换。也许最好像下面这样改变仓库代码:
private val reload = MutableLiveData<Unit>()

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

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

然后,在您的ViewModel中使用映射转换,您可以拦截发出的值:

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

fun reload() {
    repository.reload() 
}

使用该结构,您将能够在需要的时候调用ViewModel的reload()方法,从api获取最新数据并将其发布到ViewModel的data中。

reload.switchMap 显示了“未解决的引用”错误。因此,liveData(IO)emit 也显示相同的错误。 - Amir
1
这是因为缺少依赖项。在 build.gradle 中添加 implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' - aminography
1
你在ViewModel的data上设置了观察者吗? - aminography
将观察者设置在“ViewModel”内部是主要问题。这就是为什么我在我的问题中使用了“MediatorLiveData”,添加了数据源并将其删除的原因。使用您的答案,我应该在视图模型的“init”方法中为所有响应添加“data.observeForever {}”。 - Amir
1
问题是为什么你想这样做。ViewModel的目标是向视图提供有关视图生命周期的数据。在ViewModel中观察LiveData而不是在视图中观察它似乎不太合适。 - aminography
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接