https://kotlinlang.org logo
Title
t

Tin Tran

07/23/2021, 6:28 AM
Hi. After upgrading to
navigation-compose:2.4.0-alpha05
my view model
init {}
is called continuously. Does anyone having the same issue? I’m using
hilt-navigation-compose:1.0.0-alpha03
j

julioromano

07/23/2021, 7:20 AM
t

Tin Tran

07/23/2021, 7:21 AM
I’ve already checked that thread. I don’t have any navigation inside SideEffect.
j

julioromano

07/23/2021, 7:22 AM
go down
there’s also the viewmodel thing
basically, what I think it’s happening is: when the crossfade animation is ongoing, many composables will be continuously recomposed (once each frame of the animation)
This means many composable calls which before (alpha04) were happening just once, are now happening more often, if in some parts of our code we didn’t take this particularly into account it will become source of issues now that the animations have been turned on
t

Tin Tran

07/23/2021, 7:33 AM
This doesn’t seems to be the case because my code is using
HiltViewModel
. Shouldn’t its lifecycle be managed by compose navigation as well?
My route look like this
composable(
                                    Screens.Login.route,
                                    deepLinks = listOf(navDeepLink {
                                        uriPattern = Screens.Login.pattern
                                    })
                                ) {
                                    Login(
                                        viewModel = hiltViewModel(),
                                        navController = navController,
                                        modifier = Modifier
                                            .fillMaxSize()
                                    )
                                }
Hilt will help me inject the viewmodel and other dependencies too so looks like hilt is not updated to work with new version of navigation compose
j

julioromano

07/23/2021, 8:43 AM
I was using
hiltViewModel()
too, I reverted to
viewModel()
only for the bug report. You might wanna try to create a dummy viewmodel and get it with
viewModel()
to check if the issue is specific to
hiltViewModel().
My hunch is that during crossfade animation the
Login
composable is being called multiple times, which in turns calls
hiltViewModel()
multiple times. If you’re seeing multiple calls to the viewModel’s
init
it might mean that the owner’s lifecycle has already been torn down therefore
hiltViewModel()
creates a new instance of the viewModel instead of returning the last cached one.
Sometimes a workaround to this is to move the call to
hiltViewModel()
inside the
Login
composable. I know this will impact testability coz you can’t pass in another viewModel during tests, but perhaps it can be helpful to debug the issue and then file a bug report.
t

Tin Tran

07/23/2021, 8:46 AM
I’m trying narrow the issue root cause. Turns out it’s not the view model that is reinit but my navigation code is calling endlessly
@Composable
fun Login(viewModel: LoginViewModel) {
val state by viewModel.dataState.collectAsState()
  // layout code

when (state) {
        is DataState.LOADING -> {
            Loading(
                modifier = Modifier
                    .fillMaxSize()
                    .background(color = LoadingBlack)
            )
        }
        is DataState.SUCCESS<*> -> {
            Log.i("LOGIN", "Login success")
            navController.navigate(Screens.Home.route) {
                popUpTo(Screens.Login.route) {
                    inclusive = true
                }
            }
        }
        else -> {
            viewModel.reduce(LoginEvent.IDLE)
        }
    }
}
looks like it’s got recompose infinitely so the success block is called again and again
j

julioromano

07/23/2021, 8:48 AM
Yeah, that
navigate
call should be put inside a LaunchedEffect or similar.
Yes, maybe not infinitely, but perhaps once each frame of the crossfade animation
t

Tin Tran

07/23/2021, 8:49 AM
Make sense. So it get called every frame and queue up?
j

julioromano

07/23/2021, 8:50 AM
Yes, animations invalidate the screen content more often so recompositions happens a lot more during animations
navigation calls are side effects, so they should be put inside a LaunchedEffect or DisposableEffect lambda so that they are not repeatedly called on every recomposition
t

Tin Tran

07/23/2021, 8:52 AM
Is it anti pattern if I pass the nav controller to the viewmodel and let it navigate instead?
j

julioromano

07/23/2021, 8:52 AM
e.g.
is DataState.SUCCESS<*> -> {
            LaunchedEffect(state) {
                Log.i("LOGIN", "Login success")
            navController.navigate(Screens.Home.route) {
                popUpTo(Screens.Login.route) {
                    inclusive = true
                }
            }
            }
        }
Is it anti pattern if I pass the nav controller to the viewmodel and let it navigate instead?
It may break the lifetime scope of objects: The Lifetime of a view model is wider than that of the navController obj (which is tied to the fragment or activity lifetime). If you do that and your activity/fragment is recreated during a configuration change, the viewmodel wont’ be recreated and might keep using an old instance of navController while the newly instantiated activity/fragment will have a newer one.
t

Tin Tran

07/23/2021, 8:56 AM
Got it! Thanks a lot for helping me! I’m new to android and there’s so much to learn.
👌 1