Jetpack Compose条件导航优化:已登录用户直接进首页方案咨询
解决Jetpack Compose中登录状态启动闪屏问题
我开发的应用包含Login页面与Home页面,用户登录成功后跳转至Home页面,并通过DataStore保存登录状态。当前NavHost的startDestination为Login页面,应用重启时默认展示该页面,但希望已登录用户启动应用时直接进入Home页面。
尝试在onCreate方法中从DataStore获取登录状态,根据isLoggedIn参数修改startDestination,但由于状态需要默认值,出现闪屏问题:先显示默认值对应的页面,再跳转到目标页面。相关代码如下:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val appViewModel by inject<AppViewModel>() //Default value to false // (set to true happends the same, first home screen then navigates to login screen) var isLoggedIn: Boolean by mutableStateOf(false) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { appViewModel.isLoggedIn.collect { isLoggedIn = it } } } /*Collect appViewModel.isLoggedIn, which is a flow<Boolean> of preference from DataStore, containing a boolean indicating if the user has successfully logged in before*/ setContent { PrimeTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.secondary ) { // Here I call the Navhost with different startDestination based on isLoggedIn parameter RootView(isLoggedIn) } } } }
希望实现类似其他应用的效果:用户登录一次后,后续启动均直接进入Home页面,无闪屏。
方案1:先获取登录状态再初始化Compose内容
在onCreate中先通过DataStore的first()方法获取初始登录状态,再初始化Compose内容。这样可以避免默认值导致的闪屏,因为只有拿到真实状态后才会渲染页面:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val appViewModel by inject<AppViewModel>() lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { // 挂起直到获取到DataStore中的登录状态 val initialIsLoggedIn = appViewModel.isLoggedIn.first() setContent { PrimeTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.secondary ) { RootView(initialIsLoggedIn) } } } } } }
注意:first()会挂起协程直到Flow发出第一个值,不会阻塞主线程,因为我们在lifecycleScope的协程中执行。
方案2:添加加载状态,避免闪屏
在ViewModel中维护一个加载状态,在Compose中先显示加载界面,等登录状态获取完成后再渲染对应的导航页面:
1. 更新AppViewModel
class AppViewModel(private val dataStore: DataStore<Preferences>) : ViewModel() { private val _isLoadingLoginState = MutableStateFlow(true) val isLoadingLoginState = _isLoadingLoginState.asStateFlow() val isLoggedIn: Flow<Boolean> = dataStore.data .map { preferences -> // 获取到状态后标记加载完成 _isLoadingLoginState.value = false preferences[LOGGED_IN_KEY] ?: false } // 登录成功后保存状态的方法 fun saveLoginState(isLoggedIn: Boolean) { viewModelScope.launch { dataStore.edit { preferences -> preferences[LOGGED_IN_KEY] = isLoggedIn } } } companion object { private val LOGGED_IN_KEY = booleanPreferencesKey("is_logged_in") } }
2. 修改RootView
@Composable fun RootView(appViewModel: AppViewModel) { val isLoggedIn by appViewModel.isLoggedIn.collectAsStateWithLifecycle() val isLoading by appViewModel.isLoadingLoginState.collectAsStateWithLifecycle() val navController = rememberNavController() if (isLoading) { // 显示加载界面,可替换为自定义启动页 Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { CircularProgressIndicator() } } else { NavHost( navController = navController, startDestination = if (isLoggedIn) "home" else "login" ) { composable("login") { LoginScreen( onLoginSuccess = { appViewModel.saveLoginState(true) navController.navigate("home") { popUpTo("login") { inclusive = true } } } ) } composable("home") { HomeScreen( onLogout = { appViewModel.saveLoginState(false) navController.navigate("login") { popUpTo("home") { inclusive = true } } } ) } } } }
方案3:使用临时启动页作为导航起点
将NavHost的startDestination设为临时的启动页,在启动页中获取登录状态后再导航到对应页面,同时移除启动页的路由,避免返回时回到该页面:
1. 添加启动页Composable
@Composable fun SplashScreen(navController: NavHostController, appViewModel: AppViewModel) { val isLoggedIn by appViewModel.isLoggedIn.collectAsStateWithLifecycle() LaunchedEffect(isLoggedIn) { // 导航到目标页面,并移除启动页路由 navController.navigate(if (isLoggedIn) "home" else "login") { popUpTo("splash") { inclusive = true } } } // 自定义启动页UI Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text(text = "My App", style = MaterialTheme.typography.headlineLarge) } }
2. 更新NavHost配置
@Composable fun RootView(appViewModel: AppViewModel) { val navController = rememberNavController() NavHost( navController = navController, startDestination = "splash" ) { composable("splash") { SplashScreen(navController, appViewModel) } composable("login") { // 登录页面实现 } composable("home") { // 首页实现 } } }
方案选择
- 如果希望启动时立即展示应用主题的加载界面,方案2更合适;
- 如果需要自定义启动页(如品牌展示),方案3更灵活;
- 方案1实现简单,但如果DataStore读取较慢,可能会导致启动时短暂空白(可配合系统SplashScreen API优化)。
内容的提问来源于stack exchange,提问作者flopez
相关产品推荐
相关产品推荐

