I have weird behavior in my compose app, it looks like UI is being recompositioned multiple times, but I don’t really know why. Problem is related to main activity and root nav graph:
@AndroidEntryPoint
class AppMainActivity : ComponentActivity() {
private val viewModel: UserStateViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
installSplashScreen().apply {
setKeepOnScreenCondition {
Log.d("TESTOWO", "ViewModel addr in MainActivity: ${viewModel.hashCode()}")
viewModel.userState.value == UserAuthState.UNKNOWN
}
}
setContent {
CarsLocalizerTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
RootNavGraph(navController = rememberNavController(), viewModel = hiltViewModel())
}
}
}
}
}
RootNavGraph
@Composable
fun RootNavGraph(
navController: NavHostController,
viewModel: UserStateViewModel,
) {
Log.d("TESTOWO", "ViewModel addr in RootNavGraph: ${viewModel.hashCode()}")
NavHost(
navController = navController,
startDestination = if (viewModel.userState.value == UserAuthState.AUTHENTICATED) HOME_ROUTE else AUTH_ROUTE,
route = ROOT_ROUTE
) {
authNavGraph(navController = navController)
composable(route = HOME_ROUTE) {
HomeScreen()
}
}
}
UserStateViewModel
@HiltViewModel
class UserStateViewModel @Inject constructor(private val repository: UserStateRepository): ViewModel() {
private var _userState = mutableStateOf<UserAuthState>(UserAuthState.UNKNOWN)
val userState: State<UserAuthState> get() = _userState
private var authStateListenerCallback: FirebaseAuth.AuthStateListener = FirebaseAuth.AuthStateListener {
_userState.value = if (it.currentUser != null) UserAuthState.AUTHENTICATED else UserAuthState.UNAUTHENTICATED
}
init {
Log.d("TESTOWO", "View model created")
startListeningToAuthState()
}
override fun onCleared() {
super.onCleared()
stopListeningToAuthState()
}
private fun startListeningToAuthState() {
repository.setupAuthStateListener(authStateListenerCallback)
}
private fun stopListeningToAuthState() {
repository.removeAuthStateListener(authStateListenerCallback)
}
AuthNavGraph
fun NavGraphBuilder.authNavGraph(
navController: NavController
) {
navigation(
route = AUTH_ROUTE,
startDestination = Screen.Login.route
) {
composable(
route = Screen.Login.route
) {
LoginScreen(navController = navController, viewModel = hiltViewModel())
}
composable(
route = Screen.Register.route
) {
RegisterScreen(navController = navController, viewModel = hiltViewModel())
}
composable(
route = Screen.Onboarding.route
) {
OnboardingScreen(navController = navController, viewModel = hiltViewModel())
}
}
}
At app start I see the following logs:
2022-12-08 12:09:15.668 22945-22945 TESTOWO com.example.carslocalizer D View model created
2022-12-08 12:09:15.668 22945-22945 TESTOWO com.example.carslocalizer D ViewModel addr in RootNavGraph: 15070924
2022-12-08 12:09:15.695 22945-22945 TESTOWO com.example.carslocalizer D ViewModel addr in MainActivity: 15070924
2022-12-08 12:09:15.728 22945-22945 TESTOWO com.example.carslocalizer D ViewModel addr in MainActivity: 15070924
2022-12-08 12:09:15.742 22945-22945 TESTOWO com.example.carslocalizer D ViewModel addr in RootNavGraph: 15070924
2022-12-08 12:09:15.747 22945-22945 TESTOWO com.example.carslocalizer D ViewModel addr in MainActivity: 15070924
At startup I need to check if user is already authenticated. If yes, he should be navigated to home screen, if not then navigate to authentication. It works almost correct. When splash screen ends I am still able to see login view for about 1s and after that app navigates to home. I was thinking that thanks to this splash screen check
setKeepOnScreenCondition {
Log.d("TESTOWO", "ViewModel addr in MainActivity: ${viewModel.hashCode()}")
viewModel.userState.value == UserAuthState.UNKNOWN
}
when RootNavGraph
will be reached, user auth state will be already known and correct start destination will be set. But it looks like it’s not working this way… Does anybody know why UI is recompositioned so many times and why start destination is updated late?