2017-10-13 134 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只是一個示例,它可以是每個「數據輸入」。 –