MVVM pattern and startActivity



我最近决定更仔细地研究谷歌发布的新安卓架构组件,尤其是使用它们的ViewModel生命周期感知类到MVVM架构和LiveData。

只要我在处理一个"活动"或一个"片段",一切都很好。

然而,我找不到一个好的解决方案来处理"活动"切换。举个简单的例子,假设活动a有一个启动活动B的按钮。

在哪里处理startActivity()

按照MVVM模式,clickListener的逻辑应该在ViewModel中。但是,我们希望避免在其中引用"活动"。因此,将上下文传递给ViewModel不是一个选项。

我缩小了几个看起来"可以"的选项范围,但找不到任何合适的答案"这是如何做到的"。

选项1:在ViewModel中有一个枚举,其值映射到可能的路由(ACTIVITY_B、ACTIVITY_C)。将此与LiveData相结合。活动将观察此LiveData,当ViewModel决定应该启动activity_C时,它只会发布Value(activity-C)。然后,活动可以正常调用startActivity()。

选项2:常规接口模式。与选项1的原理相同,但"活动"将实现该接口。不过,我觉得这更像是一种结合。

选项3:消息选项,如Otto或类似选项。ViewModel发送一个广播,"活动"接收它并启动它必须执行的操作。此解决方案的唯一问题是,默认情况下,您应该将该广播的注册/注销器放在ViewModel中。所以没有帮助。

选项4:在某个地方有一个大的路由类,作为singleton或类似的类,可以调用它来向任何活动调度相关的路由。最终通过接口?因此,每个活动(或BaseActivity)都将实现

IRouting { void requestLaunchActivity(ACTIVITY_B); }

当你的应用程序开始有很多碎片/活动时,这种方法只是让我有点担心(因为路由类会变得巨大)

就这样,这是我的问题。你们是怎么处理的?你选择了一个我没有想到的选项吗?您认为最相关的选项是什么?为什么?谷歌推荐的方法是什么?

附言:链接没有让我任何地方1-Android ViewModel调用活动方法2-如何从一个简单的非活动java类开始一个活动?

NSimon,开始使用AAC非常棒。

我之前在aac的github上写过一期关于这一点的文章。

有几种方法可以做到这一点。

一种解决方案是使用

对保存活动上下文的NavigationController的WeakReference。这是一种常用的模式,用于处理ViewModel中与上下文绑定的内容。

出于几个原因,我强烈拒绝这样做。首先:这通常意味着你必须保留对NavigationController的引用,它修复了上下文泄漏,但根本不能解决架构问题。

(在我看来)最好的方法是使用LiveData,它具有生命周期意识,可以做所有想要的事情。

示例:

class YourVm : ViewModel() { 
val uiEventLiveData = SingleLiveData<Pair<YourModel, Int>>()
fun onClick(item: YourModel) {
uiEventLiveData.value = item to 3 // can be predefined values
}
}

在那之后,你可以倾听你内心的改变。

class YourFragmentOrActivity { 
//assign your vm whatever
override fun onActivityCreated(savedInstanceState: Bundle?) { 
var context = this
yourVm.uiEventLiveData.observe(this, Observer {
when (it?.second) {
1 -> { context.startActivity( ... ) }
2 -> { .. } 
}
})
}
}

请注意,我使用了修改过的MutableLiveData,否则它总是会为新的观察者发出最新的结果,从而导致不良行为。例如,如果您更改活动并返回,它将以循环结束。

class SingleLiveData<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
})
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
@MainThread
fun call() {
value = null
}
companion object {
private val TAG = "SingleLiveData"
}
}

为什么这种尝试比使用WeakReferences、接口或任何其他解决方案更好

因为此事件将UI逻辑与业务逻辑分离。也可以有多个观察员。它关心生命周期。它什么都不漏。

您也可以通过使用RxJava而不是使用PublishSubject的LiveData来解决此问题。(addTo需要RxKotlin)

通过在onStop()中发布订阅,注意不要泄露订阅。

class YourVm : ViewModel() { 
var subject : PublishSubject<YourItem>  = PublishSubject.create();
}
class YourFragmentOrActivityOrWhatever {
var composite = CompositeDisposable() 
onStart() { 
YourVm.subject 
.subscribe( { Log.d("...", "Event emitted $it") }, { error("Error occured $it") }) 
.addTo(compositeDisposable)         
}   
onStop() {
compositeDisposable.clear()
}
}

还要注意ViewModel绑定到活动或片段。您不能在多个活动之间共享ViewModel,因为这会破坏"生命周期意识"。

如果您需要通过使用类似房间的数据库来保持数据,或者使用地块共享数据。

您应该从活动中调用startActivity,而不是从视图模型中调用。如果你想从viewmodel打开它,你需要在viewmodel中创建带有一些导航参数的livedata,并在活动中观察livedata

您可以从具有应用程序引用的AndroidViewModel扩展ViewModel,并使用此上下文启动活动。

最新更新