AbstractSavedStateViewModelFactory:已注册具有给定键的 SavedStateProvider

AbstractSavedStateViewModelFactory: SavedStateProvider with the given key is already registered

虽然是同样的异常,但我的情况与不同,因为我使用的是 Nav-graph Scoped ViewModels,

AbstractSavedStateViewModelFactory 与 navGraphViewModels 一起使用时出现异常。
从startFragment,转到FirstPageFragment,navigateUp()回到startFragment,然后再次访问FirstPageFragment ->crash

class FirstPageFragment: Fragment() {

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

我的工厂

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")
        }
    }

...
}

这是我的 navGraph,ViewModel 用于 firstPageFragment 和 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>

我创建了一个最小示例来重现该问题。 https://github.com/yatw/saveStateHandleDemo/tree/master/app/src/main/java/com/example/savestatehandledemo
此异常仅在进入导航图时发生。

请帮忙!

所以我找到了这个异常的原因,我在 AbstractSavedStateViewModelFactory.
中将 activity 作为 SavedStateRegistryOwner 传入 第二次访问 navGraph 时,我传递了相同的 activity 和内部 class SavedStateHandleControllerSavedStateRegistry 以某种方式保存了状态。 (写这部分的人请解释并写入文档)

所以传入导航图getBackStackEntry
更新了视图模型工厂

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")
        }
    }

在片段中使用它

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")
    }

特别感谢 EpicPandaForce,

当你使用 kotlin composecompose navigation 并且想要使用 AbstractSavedStateViewModelFactory 时,你必须通过,就像接受中提到的上面的解决方案,navBackStackEntry。在 compose 中,这是在特定的 composable() { navBackStackEntry -> }

中给出的

工厂示例:

class SavedStateViewModelFactory(
private val repository: LocationRepository,
defaultArgs: Bundle? = null,
savedStateRegistryOwner: SavedStateRegistryOwner) : AbstractSavedStateViewModelFactory(
savedStateRegistryOwner,
defaultArgs) {
override fun <T : ViewModel?> create(
    key: String,
    modelClass: Class<T>,
    handle: SavedStateHandle
): T {
    return when {
        modelClass.isAssignableFrom(LocationViewModel::class.java) -> {
            LocationViewModel(repository, handle) as T
        }
        modelClass.isAssignableFrom(LocationsViewModel::class.java) -> {
            LocationsViewModel(repository, handle) as T
        }
        else -> throw IllegalArgumentException("wrong ViewModel")
    }

}}

setContent() 中的示例:

Surface(
                modifier = Modifier.fillMaxSize(),
                color = MaterialTheme.colors.background
            ) {
                val navController = rememberNavController()
                NavHost(
                    navController = navController,
                    startDestination = Screens.LocationsScreen.route,
                    ) {
                    composable(
                        route = Screens.LocationsScreen.route
                    ) { navBackStackEntry ->
                        LocationsScreen(
                            navController,
                            viewModel(
                                modelClass = LocationsViewModel::class.java,
                                factory = SavedStateViewModelFactory(
                                    repository = LocationRepositoryImpl(
                                        BarCounterDatabase.getINSTANCE(application).locationDao
                                    ),
                                    savedStateRegistryOwner = navBackStackEntry
                                )
                            )
                        )
                    }
                    composable(
                        route = Screens.LocationScreen.route+"{id}",
                        arguments = listOf(
                            navArgument(
                                name = "id"
                            ) {
                                type = NavType.LongType
                            }
                        )
                    ) { navBackStackEntry ->
                        var defaultArgs: Bundle? = null
                        navBackStackEntry ->.arguments?.getLong("id")?.let { id ->
                            defaultArgs = Bundle()
                            defaultArgs?.putLong("id", id)
                        }
                        LocationScreen(
                            navController = navController,
                            viewModel(
                                modelClass = LocationViewModel::class.java,
                                factory = SavedStateViewModelFactory(
                                    repository = LocationRepositoryImpl(
                                        BarCounterDatabase.getINSTANCE(application).locationDao
                                    ),
                                    savedStateRegistryOwner = navBackStackEntry,
                                    defaultArgs = defaultArgs
                                )
                            )
                        )
                    }
                }
            }

哦,别忘了:我只是根据 BabyishTank

的回答才弄明白的

没有那个答案我还是傻