You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:59:58