我最近决定仔细研究Google发布的新Android架构组件,特别是将他们的ViewModel生命周期感知类用于MVVM架构和LiveData。
只要我处理单个活动或单个碎片,一切都很好。
但是,我无法找到一个很好的解决方案来处理Activity切换。 比如说,为了一个简短的例子,活动A有一个按钮来启动活动B.
处理startActivity()的位置在哪里?
遵循MVVM模式,clickListener的逻辑应该在ViewModel中。但是,我们希望避免在那里引用Activity。因此,将上下文传递给ViewModel不是一种选择。
我缩小了几个似乎" OK"的选项,但未能找到任何正确的答案"这里是如何做到的。"。
选项1 :在ViewModel中有一个枚举,其值映射到可能的路由(ACTIVITY_B,ACTIVITY_C)。将此与LiveData结合使用。 活动将观察此LiveData,并且当ViewModel决定应该启动ACTIVITY_C时,它只是postValue(ACTIVITY_C)。然后,Activity可以正常调用startActivity()。
选项2 :常规界面模式。与选项1的原理相同,但Activity将实现接口。我觉得与此有点联系。
选项3 :消息选项,例如Otto或类似内容。 ViewModel发送一个Broadcast,Activity选择它并启动它所拥有的内容。此解决方案的唯一问题是,默认情况下,您应该将该广播的注册/取消注册放在ViewModel中。所以没有帮助。
选项4 :拥有一个很大的路由类,可以调用单个或类似的路由类来向任何活动分派相关路由。最终通过界面?所以每个活动(或BaseActivity)都会实现
IRouting { void requestLaunchActivity(ACTIVITY_B); }
当你的应用程序开始有很多片段/活动时,这个方法让我有点担心(因为路由类会变得非常庞大)
这就是它。这是我的问题。你们是怎么处理这个的? 你选择了我没有想到的选择吗? 您认为最相关的选项是什么?为什么? 推荐的Google方法是什么?
PS:链接并没有让我到任何地方 1 - Android ViewModel call Activity methods 2 - How to start an activity from a plain non-activity java class?
答案 0 :(得分:29)
我之前在aac' s-github写了issue。
有几种方法可以做到这一点。
一种解决方案是使用
WeakReference到一个包含活动上下文的NavigationController。这是在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,Interfaces或任何其他解决方案更好?
因为此事件将UI逻辑与业务逻辑分开。它也有可能有多个观察员。它关心生命周期。它没有泄漏任何东西。
您也可以使用PublishSubject使用RxJava而不是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绑定到Activity或Fragment。您无法在多个活动之间共享ViewModel,因为这会破坏&#34; Livecycle-Awareness&#34;。
如果您需要使用room等数据库保留数据,或使用parcel共享数据。
答案 1 :(得分:0)
您应该从活动而非视图模型调用startActivity。如果要从viewmodel中打开它,则需要在viewmodel中使用一些导航参数创建livedata并观察活动中的livedata
答案 2 :(得分:-4)
您可以从具有应用程序引用的AndroidViewModel扩展ViewModel,并使用此上下文启动活动。