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

Compose UI多次重组合及导航起始目标设置过晚问题求助

Jetpack Compose 应用重组合异常与导航起始目标延迟问题

问题描述

我的Jetpack Compose应用存在两个异常:UI发生多次重组合,且闪屏结束后会先短暂显示登录页,约1秒后才跳转到主页。核心需求是启动时检查用户认证状态:已认证则导航至主页,否则进入认证页面。原本通过闪屏的setKeepOnScreenCondition等待viewModel.userState.value从UNKNOWN变为已知状态,但实际未达到预期效果。

相关代码

AppMainActivity.kt

@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.kt

@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.kt

@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.kt

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())
        }
    }
}

启动日志

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

问题原因与解决方案

1. 多次重组合的原因

  • State驱动重组:userState是MutableState,每次值变化时,所有读取该状态的Composable(如RootNavGraph)都会触发重组。
  • 闪屏轮询机制:setKeepOnScreenCondition会被系统多次轮询检查,导致日志中多次打印MainActivity的ViewModel实例信息。
  • ViewModel实例传递冗余:Activity和RootNavGraph分别通过viewModels()和hiltViewModel()获取实例(虽日志显示为同一实例,但代码逻辑存在冗余)。

2. 导航起始目标延迟的核心原因

NavHost的startDestination仅在初始化时生效,后续userState值变化不会自动更新导航目标。即使闪屏结束时userState已变为AUTHENTICATED,NavHost仍会以初始的AUTH_ROUTE启动,之后重组时startDestination参数变化也不会触发自动导航。

修复方案

(1)统一ViewModel实例

将Activity中获取的ViewModel直接传递给RootNavGraph,避免冗余获取:

// AppMainActivity的setContent部分
RootNavGraph(navController = rememberNavController(), viewModel = viewModel)

(2)监听状态变化手动导航

在RootNavGraph中通过LaunchedEffect监听userState变化,触发手动导航:

@Composable
fun RootNavGraph(
    navController: NavHostController,
    viewModel: UserStateViewModel,
) {
    Log.d("TESTOWO", "ViewModel addr in RootNavGraph: ${viewModel.hashCode()}")

    // 监听认证状态变化,手动跳转目标页面
    LaunchedEffect(viewModel.userState) {
        val targetRoute = if (viewModel.userState.value == UserAuthState.AUTHENTICATED) HOME_ROUTE else AUTH_ROUTE
        // 避免重复导航
        if (navController.currentDestination?.route != targetRoute) {
            navController.navigate(targetRoute) {
                popUpTo(ROOT_ROUTE) { inclusive = true }
            }
        }
    }

    NavHost(
        navController = navController,
        startDestination = AUTH_ROUTE, // 初始占位,后续由LaunchedEffect修正
        route = ROOT_ROUTE
    ) {
        authNavGraph(navController = navController)
        composable(route = HOME_ROUTE) {
            HomeScreen()
        }
    }
}

(3)优化ViewModel状态初始化

主动获取当前用户状态,减少依赖Firebase AuthStateListener的延迟:

// UserStateViewModel的init块
init {
    Log.d("TESTOWO", "View model created")
    // 主动获取当前用户状态,避免等待回调
    val currentUser = repository.getCurrentUser()
    _userState.value = if (currentUser != null) UserAuthState.AUTHENTICATED else UserAuthState.UNAUTHENTICATED
    startListeningToAuthState()
}

(4)减少不必要重组

使用derivedStateOf优化状态读取,仅在userState变化时更新目标路由:

@Composable
fun RootNavGraph(
    navController: NavHostController,
    viewModel: UserStateViewModel,
) {
    val targetRoute by remember {
        derivedStateOf {
            if (viewModel.userState.value == UserAuthState.AUTHENTICATED) HOME_ROUTE else AUTH_ROUTE
        }
    }

    // ... 后续逻辑使用targetRoute
}

内容的提问来源于stack exchange,提问作者gawron103

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 08:50:27