[英]MVVM: Complex View/ViewModel -> Multiple LiveData objects?
[英]Communication between view and ViewModel in MVVM with LiveData
在ViewModel
和View
之間進行通信的正確方法是什么, Google architecture components
使用LiveData
,其中視圖訂閱更改並相應地更新自身,但是這種通信不適用於單個事件,例如顯示消息、顯示進度、隱藏進度等
在 Google 的示例中有一些像SingleLiveEvent
這樣的SingleLiveEvent
,但它僅適用於 1 個觀察者。 一些開發人員使用EventBus
但我認為當項目增長時它會很快失控。
有沒有方便正確的實現方式,你是如何實現的?
(也歡迎 Java 示例)
是的,我同意, SingleLiveEvent
是一個笨拙的解決方案,而 EventBus(以我的經驗)總是會導致麻煩。
不久前,我在閱讀 Google CodeLabs for Kotlin Coroutines 時發現了一個名為ConsumableValue
的類,我發現它是一個很好的、干凈的解決方案,對我很有幫助( ConsumableValue.kt ):
class ConsumableValue<T>(private val data: T) {
private var consumed = false
/**
* Process this event, will only be called once
*/
@UiThread
fun handle(block: ConsumableValue<T>.(T) -> Unit) {
val wasConsumed = consumed
consumed = true
if (!wasConsumed) {
this.block(data)
}
}
/**
* Inside a handle lambda, you may call this if you discover that you cannot handle
* the event right now. It will mark the event as available to be handled by another handler.
*/
@UiThread
fun ConsumableValue<T>.markUnhandled() {
consumed = false
}
}
class MyViewModel : ViewModel {
private val _oneShotEvent = MutableLiveData<ConsumableValue<String>>()
val oneShotEvent: LiveData<ConsumableValue<String>>() = _oneShotData
fun fireEvent(msg: String) {
_oneShotEvent.value = ConsumableValue(msg)
}
}
// In Fragment or Activity
viewModel.oneShotEvent.observe(this, Observer { value ->
value?.handle { Log("TAG", "Message:$it")}
})
簡而言之, handle {...}
塊只會被調用一次,因此如果您返回到屏幕,則無需清除該值。
使用 Kotlin Flow怎么樣?
我不相信它們具有與 LiveData 相同的行為,它總是為您提供最新的價值。 它只是一個類似於 LiveData 的解決方法SingleLiveEvent
的訂閱。
這是一個解釋差異的視頻,我認為您會發現有趣並回答您的問題
您可以通過不使用 LiveData 來輕松實現這一點,而是使用我專門為解決此問題而編寫的Event-Emitter庫,而不依賴 LiveData(這是 Google 概述的反模式,我不知道任何其他相關替代方案) 。
allprojects {
repositories {
maven { url "https://jitpack.io" }
}
}
implementation 'com.github.Zhuinden:event-emitter:1.0.0'
如果您還復制LiveEvent
類,那么現在您可以執行
private val emitter: EventEmitter<String> = EventEmitter()
val events: EventSource<String> get() = emitter
fun doSomething() {
emitter.emit("hello")
}
和
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = getViewModel<MyViewModel>()
viewModel.events.observe(viewLifecycleOwner) { event ->
// ...
}
}
// inline fun <reified T: ViewModel> Fragment.getViewModel(): T = ViewModelProviders.of(this).get(T::class.java)
對於基本原理,您可以查看我寫的解釋為什么替代方案不是有效方法的文章。
但是,您現在也可以使用Channel(UNLIMITED)
並使用asFlow()
將其公開為流。 這在 2019 年並不真正適用。
嘗試這個:
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
並將其包裝到 LiveData 中
class ListViewModel : ViewModel {
private val _navigateToDetails = MutableLiveData<Event<String>>()
val navigateToDetails : LiveData<Event<String>>
get() = _navigateToDetails
fun userClicksOnButton(itemId: String) {
_navigateToDetails.value = Event(itemId) // Trigger the event by setting a new Event as a new value
}
}
並觀察
myViewModel.navigateToDetails.observe(this, Observer {
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
startActivity(DetailsActivity...)
}
})
鏈接參考: 使用事件包裝器
為了在屏幕加載時顯示/隱藏進度對話框和顯示來自失敗的網絡調用的錯誤消息,您可以使用封裝了 View 正在觀察的 LiveData 的包裝器。
有關此方法的詳細信息在應用程序架構的附錄中: https : //developer.android.com/jetpack/docs/guide#addendum
定義一個資源:
data class Resource<out T> constructor(
val state: ResourceState,
val data: T? = null,
val message: String? = null
)
和一個資源狀態:
sealed class ResourceState {
object LOADING : ResourceState()
object SUCCESS : ResourceState()
object ERROR : ResourceState()
}
在 ViewModel 中,使用包含在 Resource 中的模型定義 LiveData:
val exampleLiveData = MutableLiveData<Resource<ExampleModel>>()
同樣在 ViewModel 中,定義調用 API 以加載當前屏幕數據的方法:
fun loadDataForView() = compositeDisposable.add(
exampleUseCase.exampleApiCall()
.doOnSubscribe {
exampleLiveData.setLoading()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
exampleLiveData.setSuccess(it)
},
{
exampleLiveData.setError(it.message)
}
)
)
在視圖中,在創建時設置觀察者:
viewModel.exampleLiveData.observe(this, Observer {
updateResponse(it)
})
這是示例updateResponse()
方法,顯示/隱藏進度,並在適當時顯示錯誤:
private fun updateResponse(resource: Resource<ExampleModel>?) {
resource?.let {
when (it.state) {
ResourceState.LOADING -> {
showProgress()
}
ResourceState.SUCCESS -> {
hideProgress()
// Use data to populate data on screen
// it.data will have the data of type ExampleModel
}
ResourceState.ERROR -> {
hideProgress()
// Show error message
// it.message will have the error message
}
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.