Home > Blockchain >  AbstractSavedStateViewModelFactory: SavedStateProvider with the given key is already registered
AbstractSavedStateViewModelFactory: SavedStateProvider with the given key is already registered

Time:11-10

Although it is the same exception, my situation is different from SavedStateProvider with the given key is already registered as I am using Nav-graph Scoped ViewModels,

Exception occurs when using AbstractSavedStateViewModelFactory with navGraphViewModels.
From startFragment, go to FirstPageFragment, navigateUp() back to startFragment, then visit FirstPageFragment again ->crash

class FirstPageFragment: Fragment() {

    private val myViewModel: MyViewModel by navGraphViewModels(R.id.nav_mission){
        MyViewModel.Factory(requireActivity(), "hello world1")
    }
    ...

My Factory

class MyViewModel(application: Application,
                  savedStateHandle: SavedStateHandle,
                  val someString: String) : AndroidViewModel(application){

    class Factory(val activity: Activity, val someString: String):
        AbstractSavedStateViewModelFactory(activity as SavedStateRegistryOwner, null) {

        override fun <T : ViewModel?> create(
            key: String,
            modelClass: Class<T>,
            handle: SavedStateHandle
        ): T {
            if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return MyViewModel(activity.application, handle, someString) as T
            }
            throw IllegalArgumentException("Unable to construct viewmodel")
        }
    }

...
}

This is my navGraph, ViewModel is for firstPageFragment and SecondPageFragment

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@ id/nav_main_activity"
    app:startDestination="@id/startFragment">

    <fragment
        android:id="@ id/startFragment"
        android:name="com.example.savestatehandledemo.StartFragment"
        android:label="FirstPageFragment" >
        <action
            android:id="@ id/action_startFragment_to_nav_mission"
            app:destination="@id/nav_mission" />
    </fragment>

    <navigation android:id="@ id/nav_mission"
        app:startDestination="@id/firstPageFragment">
        <fragment
            android:id="@ id/firstPageFragment"
            android:name="com.example.savestatehandledemo.FirstPageFragment"
            android:label="FirstPageFragment" >
        </fragment>
        <fragment
            android:id="@ id/secondPageFragment"
            android:name="com.example.savestatehandledemo.SecondPageFragment"
            android:label="SecondPageFragment" >
        </fragment>
    </navigation>
</navigation>

I created a minimal example to reproduce the problem. https://github.com/yatw/saveStateHandleDemo/tree/master/app/src/main/java/com/example/savestatehandledemo
This exception occur only when going into a navigation graph.

Please help!

CodePudding user response:

so I found the cause to this exception, I am passing in the activity as SavedStateRegistryOwner in AbstractSavedStateViewModelFactory.
When visiting the navGraph the second time, I am passing in the same activity and the internal class SavedStateHandleController, SavedStateRegistry somehow saved the state already. (Whoever wrote this part please explain and write into the doc)

So pass in the navGraph getBackStackEntry
Updated viewModel factory

class MyViewModel(application: Application,
                  savedStateHandle: SavedStateHandle,
                  val someString: String) : AndroidViewModel(application){

    class Factory(val application: Application,
                  val savedStateRegistryOwner: SavedStateRegistryOwner,
                  val someString: String):
        AbstractSavedStateViewModelFactory(
            savedStateRegistryOwner,
            null) {

        override fun <T : ViewModel?> create(
            key: String,
            modelClass: Class<T>,
            handle: SavedStateHandle
        ): T {
            if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return MyViewModel(application, handle, someString) as T
            }
            throw IllegalArgumentException("Unable to construct viewmodel")
        }
    }

Use it in fragment

class FirstPageFragment: Fragment() {

    private val myViewModel: MyViewModel by navGraphViewModels(R.id.nav_mission){
        MyViewModel.Factory(requireActivity().application,
            findNavController().getBackStackEntry(R.id.nav_mission),
            "hello world1")
    }

Special thanks to EpicPandaForce, https://stackoverflow.com/a/61649394/5777189

  • Related