Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IllegalArgumentException: SavedStateProvider with the given key is already registered when trying to use keyed viewmodels #2623

Closed
zskamljic opened this issue May 15, 2021 · 1 comment

Comments

@zskamljic
Copy link

zskamljic commented May 15, 2021

I have an activity like this:

@AndroidEntryPoint
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            AppTheme {
                PagesScreen()
            }
        }
    }
}

where PagesScreen looks like:

@OptIn(ExperimentalPagerApi::class)
@Composable
fun PagesScreen(pages: List<String> = listOf("one", "two", "three")) {
    Column(Modifier.fillMaxSize()) {
        val pagerState = rememberPagerState(pageCount = pages.size)
        val coroutineScope = rememberCoroutineScope()

        ScrollableTabRow(
            selectedTabIndex = pagerState.currentPage,
            Modifier.height(48.dp),
            indicator = { tabPositions ->
                TabRowDefaults.Indicator(
                    Modifier.pagerTabIndicatorOffset(pagerState, tabPositions)
                )
            },
            divider = {}
        ) {
            pages.forEachIndexed { index, page ->
                Tab(
                    selected = pagerState.currentPage == index,
                    onClick = {
                        coroutineScope.launch {
                            pagerState.animateScrollToPage(index)
                        }
                    },
                    Modifier.fillMaxHeight()
                ) {
                    Text(page)
                }
            }
        }
        HorizontalPager(state = pagerState) { page ->
            PageScreen(pages[page])
        }
    }
}

@Composable
fun PageScreen(page: String, viewModel: PageViewModel = viewModel(page)) {
   Text(text = page)
}

The idea was that each PageScreen would have it's own viewModel, as they should load data based on which page you're on. However when running this, the following exception occurs:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.company.app, PID: 8364
    java.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered
        at androidx.savedstate.SavedStateRegistry.registerSavedStateProvider(SavedStateRegistry.java:111)
        at androidx.lifecycle.SavedStateHandleController.attachToLifecycle(SavedStateHandleController.java:50)
        at androidx.lifecycle.SavedStateHandleController.create(SavedStateHandleController.java:70)
        at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:67)
        at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:84)
        at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:109)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
        at androidx.lifecycle.viewmodel.compose.ViewModelKt.get(ViewModel.kt:76)
        at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(ViewModel.kt:63)
       < stacktrace leading to PageScreen, to a line that's past the end of file >

Without passing the key to viewModel() I seem to get same instance for all. I've read that this has been fixed, but the solution was using NavHost, that I'm not using.

I'm using:

final dagger_version = '2.35'
final compose_version = '1.0.0-beta06'

implementation "com.google.dagger:hilt-android:$dagger_version"
kapt "com.google.dagger:hilt-compiler:$dagger_version"
implementation 'androidx.activity:activity-compose:1.3.0-alpha07'
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha04"
@danysantiago
Copy link
Member

Closing since this is a dupe of: #2328

We have not fixed the root issue which happens with same class ViewModel with different keys in the same ViewModelStoreOwner, we are still working on the changes needed in lifecycyle-viewmodel to be able to properly have a fix in Hilt.

With a NavHost the issue is avoided since with Navigation Compose each destination is backed by a NavBackStackEntry which itself is a ViewModelStoreOwner. In other words, because each Navigation 'screen' has its own ViewModel scope you totally avoid the issue because the ViewModel class will be unique for that store / scope. I would encourage you to take a look at using Navigation since it also has the benefit of scoping the ViewModel to where it is needed, where as right now all your ViewModels would be scoped to the Activity and in fully Compose app, it can be a lot of ViewModels retained for screens that might not be showing anymore. See: https://developer.android.com/jetpack/compose/navigation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants