web-dev-qa-db-ja.com

MVVMパターンとstartActivity

最近リリースされた新しいAndroidアーキテクチャコンポーネント、特にMVVMアーキテクチャのViewModelライフサイクル対応クラスとLiveDataを使用して)を詳しく調べることにしました。

単一のアクティビティまたは単一のフラグメントを処理している限り、すべてが問題ありません。

ただし、アクティビティの切り替えを処理するニースのソリューションは見つかりません。簡単な例のために、アクティビティAにはアクティビティBを起動するボタンがあるとします。

startActivity()はどこで処理されますか?

MVVMパターンに従って、clickListenerのロジックはViewModelにある必要があります。ただし、そこにアクティビティへの参照が含まれないようにします。したがって、コンテキストをViewModelに渡すことはオプションではありません。

「OK」と思われるいくつかのオプションを絞り込みましたが、「ここに方法はあります」という適切な答えを見つけることができませんでした。

オプション1:ViewModelに、可能なルーティング(ACTIVITY_B、ACTIVITY_C)にマッピングする値を持つ列挙型があります。これをLiveDataと組み合わせます。アクティビティはこのLiveDataを監視し、ViewModelがACTIVITY_Cを起動する必要があると判断した場合、単にpostValue(ACTIVITY_C)になります。その後、アクティビティは通常どおりstartActivity()を呼び出すことができます。

オプション2:通常のインターフェイスパターン。オプション1と同じ原理ですが、アクティビティはインターフェイスを実装します。しかし、これとはもう少し結びつきが感じられます。

オプション3:Ottoなどのメッセージングオプション。 ViewModelはBroadcastを送信し、Activityはそれを取得して、必要なものを起動します。このソリューションの唯一の問題は、デフォルトで、ViewModel内にそのブロードキャストの登録/登録解除を配置する必要があることです。だから助けにはなりません。

オプション4:シングルトンまたは類似の場所に、任意のアクティビティに関連するルーティングをディスパッチするために呼び出すことができる大きなルーティングクラスがあります。最終的にインターフェース経由?したがって、すべてのアクティビティ(またはBaseActivity)は実装します

IRouting { void requestLaunchActivity(ACTIVITY_B); }

このメソッドは、アプリが多くのフラグメント/アクティビティを開始するときに少し心配します(ルーティングクラスが巨大になるため)

これで終わりです。それが私の質問です。どうやってこれを処理しますか?私が考えもしなかったオプションを選択しますか?最も適切と思われるオプションとその理由は何ですか?推奨されるGoogleのアプローチは何ですか?

PS:どこにも行かなかったリンク1- Android ViewModel呼び出しアクティビティメソッド 2- プレーンな非アクティビティからアクティビティを開始する方法Java =クラス?

39
NSimon

NSimon、AACを使い始めるのは素晴らしいことです。

その前に、aac's-githubに issue を書きました。

それにはいくつかの方法があります。

1つの解決策は、

WeakReference アクティビティのコンテキストを保持する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 -> { .. } 
            }

        })
    }
}

Iveが変更された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、Interfaces、または他のソリューションを使用するよりも優れているのはなぜですか?

このイベントはUIロジックをビジネスロジックと分割するためです。複数のオブザーバーを持つことも可能です。ライフサイクルが重要です。何も漏れません。

PublishSubjectを使用して、LiveDataの代わりにRxJavaを使用して解決することもできます。 (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がアクティビティORフラグメントにバインドされていることに注意してください。複数のアクティビティ間でViewModelを共有することはできません。

room のようなデータベースを使用してデータを保持する必要がある場合、またはパーセルを使用してデータを共有します。

36
Emanuel S

ビューモデルからではなく、アクティビティからstartActivityを呼び出す必要があります。ビューモデルから開く場合は、いくつかのナビゲーションパラメータを使用してビューモデルにライブデータを作成し、アクティビティ内のライブデータを観察する必要があります。

0
Majid Sadeghi