2017-10-13 211 views
10

我最近决定仔细研究一下Google发布的新Android架构组件,特别是使用ViewModel生命周期感知类到MVVM架构和LiveData。MVVM模式和startActivity

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

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

startActivity()在哪里被处理?

继MVVM模式之后,clickListener的逻辑应该位于ViewModel中。但是,我们希望避免引用该活动。所以将上下文传递给ViewModel不是一种选择。

我缩小了一些看起来“确定”的选项,但无法找到任何正确的答案“这里是怎么做到的”。

选项1:在ViewModel中使用值映射到可能的路由(ACTIVITY_B,ACTIVITY_C)的枚举。将它与LiveData结合在一起。 活动将观察此LiveData,并且当ViewModel决定应该启动ACTIVITY_C时,它只是postValue(ACTIVITY_C)。然后Activity可以正常调用startActivity()。

选项2:常规界面模式。与选项1相同的原则,但活动将实施界面。尽管如此,我对此感觉更加耦合。

选项3:消息选项,如Otto或类似的。 ViewModel发送一个广播,活动将它拾起并启动它的内容。这个解决方案的唯一问题是,默认情况下,您应该将该广播的注册/取消注册放入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

谢谢。很高兴帮助你:-) –

回答

5

NSimon,其伟大的,你开始使用AAC。

在此之前,我在aac's-github中写了一个issue

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

一个解决办法是使用

WeakReference到NavigationController其保持活动的语境。这是处理ViewModel中的上下文绑定东西的常用模式。

我高度拒绝这个原因有几个。首先:通常意味着你必须保留一个引用你的NavigationController来修正上下文泄漏,但是根本不解决这个架构。

最好的方式(在我的oppinion)是使用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,接口,或任何其他解决方案?

因为此事件将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或片段。您不能在多个活动之间共享ViewModel,因为这会打破“Livecycle-Awareness”。

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

+0

感谢您的非常详细的答案。我也倾向于LiveData方法,但没有考虑到你的LiveData调整。总的来说,这一切似乎都很“黑客”,而且这样做感觉几乎不好。不管怎样,谢谢 ! (编辑:只能在20h验证赏金) – NSimon

+1

不,这不是哈克。这就是观察者模式的工作原理。你得到一个点击,将其推送到你的ViewModel,如果有一个视图(这是保证),然后它处理数据。它甚至没有补丁:)这就是它的工作原理。单击ist只是一个示例,它可以是每个“数据输入”。 –