'AbstractSavedStateViewModelFactory: SavedStateProvider with the given key is already registered
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!
Solution 1:[1]
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
Solution 2:[2]
When you use kotlin compose with compose navigation and want to use the AbstractSavedStateViewModelFactory, you have to pass, like mentioned in the accepted solution above, the navBackStackEntry. In compose this is given in the specific composable() { navBackStackEntry -> }
Example of Factory:
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")
}
}}
Example in 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
)
)
)
}
}
}
Oh, not to forget: I figured this out only with the answer of BabyishTank
Without that answer i would still be foolish
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | BabyishTank |
Solution 2 |