MVVM 模式和 startActivity
Posted
技术标签:
【中文标题】MVVM 模式和 startActivity【英文标题】:MVVM pattern and startActivity 【发布时间】:2018-03-25 10:25:15 【问题描述】:我最近决定仔细研究 Google 发布的新 android 架构组件,尤其是使用他们的 ViewModel 生命周期感知类到 MVVM 架构和 LiveData。
只要我处理单个 Activity 或单个 Fragment,一切都很好。
但是,我找不到处理活动切换的好解决方案。 比如说,为了一个简短的例子,Activity A 有一个启动 Activity B 的按钮。
startActivity() 会在哪里处理?
按照 MVVM 模式,clickListener 的逻辑应该在 ViewModel 中。但是,我们希望避免在其中引用 Activity。因此,将上下文传递给 ViewModel 不是一种选择。
我缩小了几个看起来“可以”的选项,但找不到“这里是如何做”的任何正确答案。
选项 1:在 ViewModel 中有一个枚举,其值映射到可能的路由(ACTIVITY_B、ACTIVITY_C)。将此与 LiveData 结合使用。 Activity 会观察这个 LiveData,当 ViewModel 决定应该启动 ACTIVITY_C 时,它只是 postValue(ACTIVITY_C)。然后Activity就可以正常调用startActivity()了。
选项 2 :常规界面模式。原理与选项 1 相同,但 Activity 将实现接口。不过,我觉得与此有更多的耦合。
选项 3 :消息选项,例如 Otto 或类似选项。 ViewModel 发送一个广播,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?
【问题讨论】:
【参考方案1】:NSimon,你开始使用 AAC 真是太好了。
我之前在 aac's-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,否则它总是会为新的观察者发出最新的结果,这会导致不良行为。例如,如果您更改活动并返回,它将以循环结束。
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 逻辑与业务逻辑分开。它也可以有多个观察者。它关心生命周期。它不会泄漏任何东西。
您也可以通过使用 RxJava 而不是 LiveData 通过使用 PublishSubject 来解决它。 (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,因为这会破坏“Livecycle-Awareness”。
如果您需要使用room 之类的数据库来保存您的数据,或者使用包裹共享数据。
【讨论】:
感谢您提供非常详细的回答。我也倾向于 LiveData 方法,但没有想到你的 LiveData 调整。总的来说,这一切看起来都非常“hacky”,这样做感觉几乎很糟糕。不管怎样,谢谢 ! (编辑:只能在 20 小时内验证赏金) 不,这不是 hacky。这就是观察者模式的工作原理。你得到一个点击,将它推送到你的 ViewModel,如果有一个 View(这是有保证的),那么它会处理数据。它甚至不是一个补丁 :) 这就是它的工作原理。点击只是一个示例,它可以是每个“数据输入”。 @Suman,android架构蓝图描述的很详细。我认为您将能够在链接中找到此处实现的 SingleLiveData 类。 github.com/googlesamples/android-architecture/blob/… 这正是我自从引入 Android 架构组件以来一直在使用的解决方案 :) 当我很快找到时间时,我会更新解决方案(java+kotlin)。最好的,.. 伊曼纽尔【参考方案2】:您应该从活动而不是视图模型中调用 startActivity。如果你想从 viewmodel 打开它,你需要在 viewmodel 中使用一些导航参数创建 livedata 并观察 Activity 内的 livedata
【讨论】:
检查这个问题可能对你有帮助。 ***.com/questions/46727276/…【参考方案3】:您可以从具有应用程序引用的AndroidViewModel 扩展您的 ViewModel,并使用此上下文启动活动。
【讨论】:
是的。但是,在进行单元测试时,您必须模拟上下文,这会增加额外的(不必要的)工作。我最终采用了上述方法,其中包含一个仅用于导航的 LiveData 字段。 两种方式都可以,只是添加了另一个选项。 是的,但是如果您使用该上下文开始您的活动,您需要将 NEW_TASK 标志设置为意图,这很麻烦。不应使用应用程序上下文来启动活动。以上是关于MVVM 模式和 startActivity的主要内容,如果未能解决你的问题,请参考以下文章