根Composable中注入的ViewModel生命周期异常导致免登录跳转问题的咨询
根Composable中注入的ViewModel生命周期异常导致免登录跳转问题的咨询
大家好,我最近在Jetpack Compose项目里遇到了一个很奇怪的自动跳转问题,折腾了一阵还是没搞清楚根因,想请教下社区的大佬们。
问题现象
- 首次打开应用:进入引导页(Onboarding),点击登录按钮进入登录页,输入凭证完成登录后进入主流程
- 登出流程:进入设置页点击登出,成功导航回引导页
- 异常触发:再次点击引导页的登录按钮,直接跳过登录页的凭证输入步骤,自动跳转到首页
我的架构与怀疑点
项目里用了一个NavigationRoot根Composable统一处理导航逻辑,负责给各个页面注入ViewModel、传递状态和回调函数。目前我有两个核心怀疑:
- ViewModel生命周期与根Composable绑定:LoginViewModel是在
NavigationRoot中通过Koin注入的,而根Composable的生命周期和应用一致,不会随页面回栈销毁。当用户再次进入登录页时,存活的ViewModel可能还保留着之前登录成功的状态,自动触发了跳转到首页的逻辑 - Navigation 3组件潜在bug:怀疑是Navigation 3库的回栈清空逻辑有问题,导致页面被弹出回栈时,对应的ViewModel没有被正确销毁
临时解决方案与疑问
我尝试给Koin的ViewModel注入时,添加了一个基于时间戳的动态key,这样每次进入登录页都会重新初始化LoginViewModel,问题暂时解决了。但我不确定这是不是正确的处理方式——会不会引入内存泄漏或者其他潜在问题?
相关代码(NavigationRoot)
@OptIn( ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3AdaptiveApi::class ) @Composable fun NavigationRoot(startDestination: NavKey = Onboarding) { val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() ObserveAsEvents(flow = SnackbarController.events, snackbarHostState) { event -> scope.launch { snackbarHostState.currentSnackbarData?.dismiss() val result = snackbarHostState.showSnackbar( message = event.message, actionLabel = event.action?.name, duration = when (event.duration) { Duration.SHORT -> SnackbarDuration.Short Duration.LONG -> SnackbarDuration.Long Duration.INDEFINITE -> SnackbarDuration.Indefinite } ) if (result == SnackbarResult.ActionPerformed) { event.action?.action?.invoke() } } } val backStack = rememberNavBackStack(startDestination) val listDetailStrategy = rememberListDetailSceneStrategy<Any>() LaunchedEffect(startDestination) { if (backStack.firstOrNull() != startDestination) { backStack.clear() backStack.add(startDestination) } } @Composable fun AppScaffold(modifier: Modifier = Modifier) { Scaffold( modifier = modifier .fillMaxSize(), contentWindowInsets = WindowInsets(0, 0, 0, 0), snackbarHost = { SnackbarHost(snackbarHostState) }, content = { contentPadding -> NavDisplay( modifier = Modifier .padding(contentPadding) .consumeWindowInsets(contentPadding), backStack = backStack, onBack = { keysToRemove -> repeat(keysToRemove) { backStack.removeLastOrNull() } }, entryProvider = entryProvider { entry<Onboarding> { val viewModel = koinViewModel<OnboardingViewModel>() val state = viewModel.state.collectAsStateWithLifecycle() OnboardingScreen( stateProvider = { state }, onAction = viewModel::onAction, uiEvent = viewModel.uiEvent, onNavigate = { dest -> backStack.apply { if (dest is ServerList) clear() add(dest) } } ) } entry<Login> { val viewModel = koinViewModel<LoginViewModel>() val state = viewModel.state.collectAsStateWithLifecycle() LoginScreen( stateProvider = { state }, uiEvent = viewModel.uiEvent, onAction = viewModel::onAction, onNavigate = { dest -> backStack.clear() backStack.add(dest) } ) } entry<Register> { val viewModel = koinViewModel<RegisterViewModel>() val state = viewModel.state.collectAsStateWithLifecycle() RegisterScreen( stateProvider = { state }, uiEvent = viewModel.uiEvent, onAction = viewModel::onAction, onNavigate = { dest -> backStack.clear() backStack.add(dest) } ) } entry<ServerList>(metadata = ListDetailSceneStrategy.listPane()) { val viewModel = koinViewModel<ServerViewModel>() val state = viewModel.state.collectAsStateWithLifecycle() val paging = viewModel.paging.collectAsLazyPagingItems() ServerScreen( stateProvider = { state }, uiEvent = viewModel.uiEvent, onAction = viewModel::onAction, pagedList = paging, onNavigate = { dest -> if (dest is ServerDetails && backStack.lastOrNull() is ServerDetails) { backStack[backStack.lastIndex] = dest } else backStack.add(dest) } ) } entry<ServerDetails>(metadata = ListDetailSceneStrategy.detailPane()) { key -> val viewModel: ServerDetailsViewModel = koinViewModel( key = key.id.toString() ) { parametersOf(key.id, key.name) } val state = viewModel.state.collectAsStateWithLifecycle() ServerDetailsScreen( stateProvider = { state }, uiEvent = viewModel.uiEvent, onAction = viewModel::onAction, onNavigate = { dest -> backStack.add(dest) } ) } entry<Settings> { val viewModel = koinViewModel<SettingsViewModel>() val state = viewModel.state.collectAsStateWithLifecycle() SettingsScreen( stateProvider = { state }, uiEvent = viewModel.uiEvent, onAction = viewModel::onAction, onNavigate = { dest -> backStack.clear() backStack.add(dest) } ) } } ) } ) } AppScaffold() }
内容来源于stack exchange
相关产品推荐
相关产品推荐

