Android 使用LiveData在MVVM中实现视图和视图模型之间的通信

Android 使用LiveData在MVVM中实现视图和视图模型之间的通信,android,android-livedata,android-architecture-components,Android,Android Livedata,Android Architecture Components,在视图模型和视图之间进行通信的正确方式是什么,谷歌架构组件使用动态数据,其中视图订阅更改并相应地更新自身,但这种通信不适用于单个事件,例如显示消息、显示进度,隐藏进度等 谷歌的例子中有一些类似于SingleLiveEvent的黑客,但它只对一个观察者有效。 一些开发人员使用EventBus,但我认为当项目增长时,它会很快失控 是否有一种方便和正确的方法来实现它,您如何实现它 (也欢迎使用Java示例)是的,我同意,SingleLiveEvent是一个很难解决的解决方案,而EventBus(以我的

视图模型
视图
之间进行通信的正确方式是什么,
谷歌架构组件
使用
动态数据
,其中视图订阅更改并相应地更新自身,但这种通信不适用于单个事件,例如显示消息、显示进度,隐藏进度等

谷歌的例子中有一些类似于
SingleLiveEvent
的黑客,但它只对一个观察者有效。 一些开发人员使用
EventBus
,但我认为当项目增长时,它会很快失控

是否有一种方便和正确的方法来实现它,您如何实现它


(也欢迎使用Java示例)

是的,我同意,
SingleLiveEvent
是一个很难解决的解决方案,而EventBus(以我的经验)总是会带来麻烦

不久前,我在阅读Google CodeLabs for Kotlin Coroutines时发现了一个名为
ConsumableValue
的类,我发现它是一个很好的、干净的解决方案,对我很有用():

简而言之,
句柄{…}
块只会被调用一次,因此,如果返回到屏幕,则无需清除该值。

尝试以下操作:

/**
 * 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
}
链接参考:

使用Kotlin怎么样

我不相信他们的行为与LiveData的行为相同,因为LiveData总能为您提供最新的价值。它只是一个订阅,类似于LiveData的变通方法
SingleLiveEvent

这是一段视频,解释了其中的差异,我想你会发现这很有趣,并回答你的问题


要显示/隐藏进度对话框以及在加载屏幕时显示来自失败网络调用的错误消息,可以使用封装视图正在观察的LiveData的包装器

有关此方法的详细信息,请参见应用程序体系结构附录:

定义资源:

data class Resource<out T> constructor(
        val state: ResourceState,
        val data: T? = null,
        val message: String? = null
)
val exampleLiveData = MutableLiveData<Resource<ExampleModel>>()
在ViewModel中,使用包装在资源中的模型定义LiveData:

data class Resource<out T> constructor(
        val state: ResourceState,
        val data: T? = null,
        val message: String? = null
)
val exampleLiveData = MutableLiveData<Resource<ExampleModel>>()
在视图中,在创建时设置观察者:

    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
            }
        }
    }
}
private-fun更新响应(资源:资源?){
资源?让我们{
何时(它的状态){
ResourceState.LOADING->{
showProgress()
}
ResourceState.SUCCESS->{
hideProgress()
//使用数据填充屏幕上的数据
//it.data将具有ExampleModel类型的数据
}
ResourceState.ERROR->{
hideProgress()
//显示错误消息
//it.message将显示错误消息
}
}
}
}

您可以不使用LiveData,而使用

如果你也复制,那么现在你就可以了

private val emitter: EventEmitter<String> = EventEmitter()
val events: EventSource<String> get() = emitter

fun doSomething() {
    emitter.emit("hello")
}
private val发射器:EventEmitter=EventEmitter()
val事件:EventSource get()=发射器
有趣的事{
emitter.emit(“你好”)
}

覆盖已创建的视图(视图:视图,savedInstanceState:Bundle?){
super.onViewCreated(视图,savedInstanceState)
viewModel=getViewModel()
viewModel.events.observe(viewLifecycleOwner){event->
// ...
}
}
//内联fun Fragment.getViewModel():T=ViewModelProviders.of(this.get)(T::class.java)
有关基本原理,您可以查看


但是,您现在也可以使用
频道(无限)
并使用
asFlow()
将其作为流公开。这在2019年就不适用了。

我目前使用的是一种非常有趣的解决方案流!对于显示对话框这样的单个事件,这实际上不起作用,对吗?我的意思是,它将显示一个对话框,但如果您例如得到一个错误->显示一个对话框->离开屏幕->返回,当再次附加观察者时,它将接收缓存值(错误)并再次显示对话框,除非您在返回时清除LiveData的值。或者我遗漏了什么?@patrick.elmquist通常使用这种模式,在创建视图时,您既要设置观察者,也要进行新的API调用,因此调用
loadDataForView()
方法。此模式已广泛应用于一个安装量超过100万的应用程序中,因此经过了战斗测试,效果良好!
val exampleLiveData = MutableLiveData<Resource<ExampleModel>>()
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)
    })
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
            }
        }
    }
}
allprojects {
    repositories {
        maven { url "https://jitpack.io" }
    }
}

implementation 'com.github.Zhuinden:event-emitter:1.0.0'
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)