MVVM模式和startActivity

NSi*_*mon 40 android mvvm android-architecture-components

我最近决定仔细研究一下Google发布的新Android架构组件,特别是将他们的ViewModel生命周期感知类用于MVVM架构和LiveData.

只要我处理单个活动或单个碎片,一切都很好.

但是,我找不到一个很好的解决方案来处理Activity切换.比如说,为了一个简短的例子,活动A有一个按钮来启动活动B.

startActivity()在哪里处理?

遵循MVVM模式,clickListener的逻辑应该在ViewModel中.但是,我们希望避免在那里引用Activity.因此,将上下文传递给ViewModel不是一种选择.

我缩小了几个似乎"没问题"的选项,但却找不到任何正确的答案"这里是怎么做的".

选项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); }
Run Code Online (Sandbox Code Playgroud)

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

就是这样了.那是我的问题.你们是怎么处理这个的?你选择了我没想过的选择吗?您认为最相关的选项是什么?为什么?推荐的Google方法是什么?

PS:没有把我带到任何地方的链接1 - Android ViewModel调用Activity方法 2 - 如何从普通的非活动java类启动活动?

Ema*_*l S 39

NSimon,你开始使用AAC很棒.

我之前在aac's-github上写了一个问题.

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

一种解决方案是使用a

对包含Activity的Context的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
    }
}
Run Code Online (Sandbox Code Playgroud)

之后,您可以在视图中收听更改.

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 -> { .. } 
            }

        })
    }
}
Run Code Online (Sandbox Code Playgroud)

注意我已使用修改过的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"
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么尝试使用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()
       }
    }
Run Code Online (Sandbox Code Playgroud)

还要注意ViewModel绑定到Activity或Fragment.您无法在多个活动之间共享ViewModel,因为这会破坏"Liv​​ecycle-Awareness".

如果需要,可以使用像room这样的数据库来保存数据,或者使用parcel共享数据.

  • 不,这不是hacky.这就是观察者模式的运作方式.您点击一下,将其推送到ViewModel,如果有View(这是有保证的),那么它会处理数据.它甚至不是补丁:)这是如何工作的.点击ist只是一个示例,它可以是每个"数据输入". (3认同)
  • @Suman,android架构蓝图非常详细地描述了它.我想你将能够在链接中找到这里实现的SingleLiveData类.https://github.com/googlesamples/android-architecture/blob/todo-mvvm-live-kotlin/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.kt (3认同)