MVVM 模式和 startActivity
MVVM pattern and startActivity
我最近决定仔细研究 Google 发布的新 Android 架构组件,尤其是将其 ViewModel 生命周期感知 class 用于 MVVM 架构,以及 LiveData .
只要我处理的是单个 Activity 或单个 Fragment,一切都很好。
但是,我找不到一个很好的解决方案来处理 Activity 切换。
举个简短的例子,Activity A 有一个启动按钮 Activity B.
startActivity()在哪里处理?
按照MVVM模式,clickListener的逻辑应该在ViewModel中。但是,我们希望避免在其中引用 Activity。因此,将上下文传递给 ViewModel 不是一种选择。
我缩小了几个看起来 "OK" 的选项,但找不到 "here's how to do it." 的任何正确答案。
选项 1:在 ViewModel 中有一个枚举,其值映射到可能的路由(ACTIVITY_B、ACTIVITY_C)。将其与 LiveData 相结合。
activity 会观察这个 LiveData,当 ViewModel 决定应该启动 ACTIVITY_C 时,它只是 postValue(ACTIVITY_C)。 Activity 然后可以正常调用 startActivity()。
选项 2 : 常规界面模式。与选项 1 的原理相同,但 Activity 将实现该接口。不过我觉得有点耦合。
选项 3:消息传递选项,例如 Otto 或类似选项。 ViewModel 发送一个广播,Activity 接收它并启动它必须执行的操作。此解决方案的唯一问题是,默认情况下,您应该将该广播的 register/unregister 放入 ViewModel 中。所以没有帮助。
选项 4:在某个地方有一个大路由 class,作为单例或类似的,可以调用它来将相关路由分派给任何 activity .最终通过界面?所以每个 activity(或一个 BaseActivity)都会实现
IRouting { void requestLaunchActivity(ACTIVITY_B); }
当你的应用程序开始有很多 fragments/activities 时,这种方法让我有点担心(因为路由 class 会变得很庞大)
就是这样。那是我的问题。你们是怎么处理的?
你会选择我没有想到的选项吗?
您认为哪个选项最相关,为什么?
推荐的 Google 方法是什么?
PS :没有让我到任何地方的链接
1 - Android ViewModel call Activity methods
2 -
NSimon,很高兴你开始使用 AAC。
之前我在aac的-github里写了一个issue
有几种方法可以做到这一点。
一个解决方案是使用
WeakReference 到保存 Activity 上下文的 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,否则它将始终为新观察者发出最新结果,从而导致不良行为。例如,如果您更改 activity 并返回,它将以循环结束。
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"
}
}
为什么这种尝试比使用弱引用、接口或任何其他解决方案更好?
因为此事件将 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 或片段。您不能在多个 Activity 之间共享 ViewModel,因为这会破坏 "Livecycle-Awareness".
如果您需要通过使用像 room 这样的数据库来保存您的数据,或者使用包裹共享数据。
您可以从具有应用程序引用的 AndroidViewModel 扩展您的 ViewModel,并使用此上下文启动 activity。
您应该从 activity 调用 startActivity,而不是从视图模型。如果你想从 viewmodel 打开它,你需要在 viewmodel 中使用一些导航参数创建 livedata 并观察 activity
中的 livedata
我最近决定仔细研究 Google 发布的新 Android 架构组件,尤其是将其 ViewModel 生命周期感知 class 用于 MVVM 架构,以及 LiveData .
只要我处理的是单个 Activity 或单个 Fragment,一切都很好。
但是,我找不到一个很好的解决方案来处理 Activity 切换。 举个简短的例子,Activity A 有一个启动按钮 Activity B.
startActivity()在哪里处理?
按照MVVM模式,clickListener的逻辑应该在ViewModel中。但是,我们希望避免在其中引用 Activity。因此,将上下文传递给 ViewModel 不是一种选择。
我缩小了几个看起来 "OK" 的选项,但找不到 "here's how to do it." 的任何正确答案。
选项 1:在 ViewModel 中有一个枚举,其值映射到可能的路由(ACTIVITY_B、ACTIVITY_C)。将其与 LiveData 相结合。 activity 会观察这个 LiveData,当 ViewModel 决定应该启动 ACTIVITY_C 时,它只是 postValue(ACTIVITY_C)。 Activity 然后可以正常调用 startActivity()。
选项 2 : 常规界面模式。与选项 1 的原理相同,但 Activity 将实现该接口。不过我觉得有点耦合。
选项 3:消息传递选项,例如 Otto 或类似选项。 ViewModel 发送一个广播,Activity 接收它并启动它必须执行的操作。此解决方案的唯一问题是,默认情况下,您应该将该广播的 register/unregister 放入 ViewModel 中。所以没有帮助。
选项 4:在某个地方有一个大路由 class,作为单例或类似的,可以调用它来将相关路由分派给任何 activity .最终通过界面?所以每个 activity(或一个 BaseActivity)都会实现
IRouting { void requestLaunchActivity(ACTIVITY_B); }
当你的应用程序开始有很多 fragments/activities 时,这种方法让我有点担心(因为路由 class 会变得很庞大)
就是这样。那是我的问题。你们是怎么处理的? 你会选择我没有想到的选项吗? 您认为哪个选项最相关,为什么? 推荐的 Google 方法是什么?
PS :没有让我到任何地方的链接
1 - Android ViewModel call Activity methods
2 -
NSimon,很高兴你开始使用 AAC。
之前我在aac的-github里写了一个issue
有几种方法可以做到这一点。
一个解决方案是使用
WeakReference 到保存 Activity 上下文的 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,否则它将始终为新观察者发出最新结果,从而导致不良行为。例如,如果您更改 activity 并返回,它将以循环结束。
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"
}
}
为什么这种尝试比使用弱引用、接口或任何其他解决方案更好?
因为此事件将 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 或片段。您不能在多个 Activity 之间共享 ViewModel,因为这会破坏 "Livecycle-Awareness".
如果您需要通过使用像 room 这样的数据库来保存您的数据,或者使用包裹共享数据。
您可以从具有应用程序引用的 AndroidViewModel 扩展您的 ViewModel,并使用此上下文启动 activity。
您应该从 activity 调用 startActivity,而不是从视图模型。如果你想从 viewmodel 打开它,你需要在 viewmodel 中使用一些导航参数创建 livedata 并观察 activity
中的 livedata