web-dev-qa-db-ja.com

Android共有ビューモデルのナビゲーションコンポーネント

ビューモデルは、それがアタッチされているアクティビティまたはフラグメントとともに生存し、終了します。これには、私を超えて、誰もが質問しないのはなぜかという特定の影響があります(ナビゲーションアーキテクチャを全体像にとらえると)。

最新のAndroidブログとナビゲーションフレームワークのしくみによると、単一のアクティビティの複数のフラグメントの節に進むことをお勧めします。

たぶん私は以下のアプリデザインを持っています。

Activity A (Application Entry Point)
----------
Fragment (A) (Uses ViewModel AB)
Fragment (B) (Uses ViewModel AB)
Fragment (C) (Uses ViewModel CDE)
Fragment (D) (Uses ViewModel CDE)
Fragment (E) (Uses ViewModel CDE)

共有ビューモデルを使用しているため、ビューモデルがアクティビティにアタッチされます。しかし、これは漏れやすいようです。私がAからEまでずっとトラバースして、フラグメントからフラグメントBに飛び移って戻ってきた場合のように、ビューモデルCDEは破棄されるべきですが、アクティビティに接続されているため、そうではありません。

また、データを共有するため、ビューモデルをフラグメントに接続できません。

私だけがこの質問を提起しているという事実は、私が理解しているここで私が間違っていると信じさせます。状況について適切な洞察を得ることができれば、うれしいでしょう。

すべてのLifecycleOwner(つまり、フラグメントまたはアクティビティ)は、clear()関数を持つViewModelStoreにモデルを保持します。ただし、クリアすると、ViewModelStoreからすべてのモデルがスイープされます。これは、ケースでは望ましくありません(ViewModel ABとViewModel CDEの両方がアクティビティのViewModelStoreからクリアされます)。この問題の考えられる解決策の1つは、必要なときに安全にクリアできるViewModelごとのストアを用意することです。

_class MainActivity : AppCompatActivity() {

val individualModelStores = HashMap<KClass<out ViewModel>, ViewModelStore>()

inline fun <reified VIEWMODEL : ViewModel> getSharedViewModel(): VIEWMODEL {
    val factory = object : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            //Put your existing ViewModel instantiation code here,
            //e.g., dependency injection or a factory you're using
            //For the simplicity of example let's assume
            //that your ViewModel doesn't take any arguments
            return modelClass.newInstance()
        }
    }

    val viewModelStore = [email protected]<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.Java)
}

    val viewModelStore = [email protected]<VIEWMODEL>()
    return ViewModelProvider(this.getIndividualViewModelStore<VIEWMODEL>(), factory).get(VIEWMODEL::class.Java)
}

inline fun <reified VIEWMODEL : ViewModel> getIndividualViewModelStore(): ViewModelStore {
    val viewModelKey = VIEWMODEL::class
    var viewModelStore = individualModelStores[viewModelKey]
    return if (viewModelStore != null) {
        viewModelStore
    } else {
        viewModelStore = ViewModelStore()
        individualModelStores[viewModelKey] = viewModelStore
        return viewModelStore
    }
}

inline fun <reified VIEWMODEL : ViewModel> clearIndividualViewModelStore() {
    val viewModelKey = VIEWMODEL::class
    individualModelStores[viewModelKey]?.clear()
    individualModelStores.remove(viewModelKey)
}
_

}

getSharedViewModel()を使用して、アクティビティのライフサイクルにバインドされているViewModelのインスタンスを取得します。

_val viewModelCDE : ViewModelCDE = (requireActivity() as MainActivity).getSharedViewModel(/*There could be some arguments in case of a more complex ViewModelProvider.Factory implementation*/)
_

後で、共有ViewModelを破棄するときが来たら、clearIndividualViewModelStore<>()を使用します。

_(requireActivity() as MainActivity).clearIndividualViewModelStore<ViewModelCDE>()
_

不要になった場合は、ViewModelをできるだけ早くクリアしたい場合があります(たとえば、ユーザー名やパスワードなどの機密性の高いユーザーデータが含まれている場合)。フラグメントの切り替えごとにindividualModelStoresの状態をログに記録して、共有ViewModelを追跡できるようにする方法を次に示します。

_override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    if (BuildConfig.DEBUG) {
        navController.addOnDestinationChangedListener { _, _, _ ->
            if (individualModelStores.isNotEmpty()) {
                val tag = [email protected]
                Log.w(
                        tag,
                        "Don't forget to clear the shared ViewModelStores if they are not needed anymore."
                )
                Log.w(
                        tag,
                        "Currently there are ${individualModelStores.keys.size} ViewModelStores bound to ${[email protected]}:"
                )
                for ((index, viewModelClass) in individualModelStores.keys.withIndex()) {
                    Log.w(
                            tag,
                            "${index + 1}) $viewModelClass\n"
                    )
                }
            }
        }
    }
}
_
0
Alex Kuzmin

または、提案された解決策を実行できない場合は、共有ViewModelがスコープされているActivityレベルでViewModelをクリアするだけです。

これを行うと、次のようになります。

getActivity().getViewModelStore().clear();

これにより、共有ビューモデルのデータが確実にクリアされます。

0
Sakiboy

これはあなたの問題だと思いました:

私がAからEまでずっとトラバースして、フラグメントからフラグメントBに飛び移って戻ってきた場合のように、ビューモデルCDEは破棄されるべきですが、アクティビティに接続されているため、そうではありません。

ViewModelを使用して複数のフラグメント間でデータを共有したいが、フラグメントが特定の画面に移動したときにViewModelのデータがDestroyになるようにしたい。

このための私の提案ソリューションは:

  1. 値を上書きしてViewModelのデータを破棄するDestroy Data Function"空の値に上書きして、ViewModelのデータを破棄します" "など

    class CDEViewModel : ViewModel() {  
       var dataString: String = ""
    
       fun destroyViewModelData() { // Function that will Destroythe Data
           dataString= ""
       }
    }
    
  2. これで、ViewModelデータがClear/Destroyであることを確認する必要があるときはいつでも、フラグメントでdestroyViewModelData関数を呼び出すことができます。

    class FragmentE {
    
    private lateinit var cdeViewModel : CDEViewModel 
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Initialize your ViewModel
        cdeViewModel = ViewModelProviders.of(this).get(CDEViewModel ::class.Java)
    }
    
    override fun onStart() {
        super.onStart()
    
        // Set your Value Here
        cdeViewModel.dataString = "String 1"
    }
    
    override fun onStop() {
        super.onStop()
    
        // Reset/Destroy Data when Screen is Being Close/Navigate to other Screen
        // After Call this function, in Whatever Screen, the ViewModel previous Set ""String 1"" Data is Clear/Destroy and become "" empty value.
        cdeViewModel.destroyViewModelData()
    }
    }
    

あなたの場合、あなたはdestroyViewModelData function at FragmentEのonStop()で呼び出すことができるので、FragmentEからFragmentBに移動するがある場合、CDEViewModelのデータはすべて""空の文字列これはリセット/破棄であることを意味します。

このシンプルなソリューションがお役に立てば幸いです。ありがとうございました。

0
I am a Student

_Navigation 2.1.0-alpha02_(_2.1.0_で安定)なので、by navGraphViewModels()を使用して、ナビゲーショングラフのレベルでスコープを持つViewModelを作成できます。

アクティビティまたは単一のフラグメントにアタッチされないViewModelを取得するには、ネストされたナビゲーショングラフを作成し、そのグラフのスコープでViewModelのインスタンスを要求する必要があります。これにより、ネストされたナビゲーショングラフ内にいる間、ViewModelがライブになり、ネストされたグラフ内のフラグメントがViewModelの同じインスタンスを再利用します。

このようにして、いくつかのネストされたナビゲーショングラフを作成できます。それぞれに、そのグラフを構成するフラグメント間で共有されるViewModelの単一のインスタンスがあります。

同じフラグメントとViewModelの分布に従います。

_MainActivity (Application Entry Point)
----------
Fragment (A) (Uses SharedViewModelOne) -> navGraphOne
Fragment (B) (Uses SharedViewModelOne) -> navGraphOne
Fragment (C) (Uses SharedViewModelTwo) -> navGraphTwo
Fragment (D) (Uses SharedViewModelTwo) -> navGraphTwo
_

これを達成するには、次の手順に従う必要があります。

  1. あなたのbuild.gradle(Module)はこのようになるはずです

    _...
    apply plugin: 'kotlin-kapt'
    
    Android {
        ...
        kotlinOptions {
            jvmTarget = "1.8"
        }
    }
    
    dependencies{
        ...
        implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
        kapt 'androidx.lifecycle:lifecycle-compiler:2.2.0'
        implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
        implementation 'androidx.navigation:navigation-ui-ktx:2.2.1'
    }
    _
  2. 同じViewModelを共有するフラグメントを選択し、ネストされたナビゲーショングラフに追加します。これを行うには、ナビゲーショングラフデザイナーでフラグメントを選択し、それらを右クリックして_Move to Nested Graph_を選択します

    この例では、FragmentAとFragment BをnavGraphOneとFragmentCに追加し、
    navGraphTwoへのフラグメントD。

    ネストされたナビゲーショングラフの詳細については、こちらをご覧ください ここ

  3. フラグメントAとフラグメントBで、SharedViewModelOneのインスタンスを要求します。

    _private val modelOne: SharedViewModelOne by navGraphViewModels(R.id.navGraphOne) {
        //defaultViewModelProviderFactory or the ViewModelProvider.Factory you are using.
        defaultViewModelProviderFactory
    }
    
    override fun onCreateView(
        ..
    ): View? {
        ...
        //use binding.lifecycleOwner = viewLifecycleOwner
        //to make sure the observer disappears when the fragment is destroyed
        modelOne.item.observe(viewLifecycleOwner, Observer {
            //do Something
        })
        ...
    }
    _
  4. フラグメントCとフラグメントDで、SharedViewModelTwoのインスタンスを要求します。

    _private val modelTwo: SharedViewModelTwo by navGraphViewModels(R.id.navGraphTwo) {
        //defaultViewModelProviderFactory or the ViewModelProvider.Factory you are using.
        defaultViewModelProviderFactory
    }
    
    override fun onCreateView(
        ..
    ): View? {
        ...
        //use binding.lifecycleOwner = viewLifecycleOwner
        //to make sure the observer disappears when the fragment is destroyed
        modelTwo.item.observe(viewLifecycleOwner, Observer {
            //do Something
        })
        ...
    }
    _
  5. 次に、ViewModelの単一のインスタンスのみが作成され、フラグメント間で共有されていることを確認するには、onCleared()メソッドをオーバーライドし、ViewModelの_init{}_にチェックポイントを追加します。

    例えば:

    _class SharedViewModelOne : ViewModel() {
    
        private val _item = MutableLiveData<String>()
        val item : LiveData<String>
            get() = _item
    
        init {
            Log.d(TAG, "SharedViewModelOne has created!")
        }
    
        override fun onCleared() {
            super.onCleared()
            Log.d(TAG, "SharedViewModelOne has removed!")
        }
    }
    _

前の手順を実行した後、同じネストされたナビゲーショングラフに属するフラグメント間で共有されるViewModelを作成できるはずです。ViewModelは、グラフ内にいる間のみ存続します。そのままにしておくと、破壊されました。

何かがあまり明確でないと感じた場合は、この repo を確認して、疑いを明確にすることができます。

0