Android Jetpack Compose应用登录屏短暂显示后跳转主屏问题
解决Jetpack Compose中已登录用户启动时登录屏幕短暂闪烁的问题
问题根源
当前实现的核心问题是:NavHost默认将登录页设为启动目标,登录页的UI已经开始渲染后,才在LaunchedEffect中执行登录状态检查并跳转。这就导致登录屏幕会短暂显示后才切换到主屏幕。
解决方案
1. 顶层提前完成认证状态检查,添加加载过渡
在NavHost的父级Composable中先完成用户登录状态的检查,期间显示加载界面(如应用Logo、加载提示),等检查完成后再初始化NavHost并直接设置正确的启动目标。
2. 修正核心代码
@Composable fun MainApp() { val context = LocalContext.current.applicationContext // 修正原代码中authClient初始化的错误(原代码会丢弃EmailAuthClient实例) val authClient by lazy { GoogleAuthClient( context = context, authClient = Identity.getSignInClient(context) ) } val navController = rememberNavController() // 管理认证检查状态与登录状态 val isCheckingAuth = remember { mutableStateOf(true) } val loggedInUser = remember { mutableStateOf<User?>(null) } // 初始化时同步检查登录状态 LaunchedEffect(key1 = Unit) { loggedInUser.value = authClient.getSignedInUser() isCheckingAuth.value = false } // 认证检查完成前显示加载界面 if (isCheckingAuth.value) { SplashScreen() } else { NavHost( navController = navController, // 根据登录状态直接设置启动目标 startDestination = if (loggedInUser.value != null) { "${Screen.HOME_SCREEN}/${loggedInUser.value!!.userId}" } else { Screen.LOGIN_SCREEN } ) { composable(Screen.LOGIN_SCREEN) { val signInViewModel = hiltViewModel<SignInViewModel>() val state by signInViewModel.state.collectAsStateWithLifecycle() val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.StartIntentSenderForResult(), onResult = { result -> if(result.resultCode == RESULT_OK){ lifecycleScope.launch { val signInResult = authClient.signedInWithIntent( intent = result.data ?: return@launch ) signInViewModel.onSignIn(signInResult) } } } ) LaunchedEffect(key1 = state.isSignInSuccess) { if(state.isSignInSuccess){ val user = authClient.getSignedInUser() user?.let { Toast.makeText( context, "Sign in successful", Toast.LENGTH_LONG ).show() navController.navigate("${Screen.HOME_SCREEN}/${user.userId}") { // 清空回退栈,避免返回登录页 popUpTo(Screen.LOGIN_SCREEN) { inclusive = true } } signInViewModel.resetState() } } } AuthenticationScreen( state = state, onGoogleClick = { lifecycleScope.launch { val signInIntentSender = authClient.signIn() launcher.launch( IntentSenderRequest.Builder( signInIntentSender ?: return@launch ).build() ) } } ) } composable("${Screen.HOME_SCREEN}/{userId}", arguments = listOf(navArgument("userId"){ type = NavType.StringType }) ){ backStackEntry -> val userId = backStackEntry.arguments?.getString("userId") ?: return@composable val notesViewModel = hiltViewModel<NotesViewModel>() val onSignOut : () -> Unit = { lifecycleScope.launch { authClient.signOut() Toast.makeText( context, "Signed out", Toast.LENGTH_LONG ).show() navController.navigate(Screen.LOGIN_SCREEN) { // 清空回退栈,避免返回主屏幕 popUpTo(Screen.LOGIN_SCREEN) { inclusive = true } } } } HomeScreen( navController = navController, userId = userId, viewModel = notesViewModel, userData = loggedInUser.value, onSignOut = onSignOut ) } } } } @Composable private fun SplashScreen() { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { // 可替换为应用Logo或自定义加载动画 Text(text = "加载中...") } }
关键优化点
- 提前检查状态:在
NavHost初始化前完成登录状态检查,避免登录页被渲染 - 加载过渡:检查期间显示加载界面,提升用户体验
- 清空回退栈:登录成功和退出登录时添加
popUpTo配置,防止用户通过返回键回到不需要的页面 - 移除冗余检查:删除登录页内原有的
LaunchedEffect登录状态检查逻辑,避免重复操作
补充说明
如果你的getSignedInUser()是异步方法(比如依赖Firebase Auth的异步监听),可以改用Flow监听认证状态:
// 在authClient中暴露认证状态Flow val authStateFlow = authClient.authStateFlow val currentUser by authStateFlow.collectAsStateWithLifecycle(initialValue = null) LaunchedEffect(key1 = currentUser) { isCheckingAuth.value = false }
内容的提问来源于stack exchange,提问作者Vbcc Vbccc
相关产品推荐
相关产品推荐

