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