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

根Composable中注入的ViewModel生命周期异常导致免登录跳转问题的咨询

根Composable中注入的ViewModel生命周期异常导致免登录跳转问题的咨询

大家好,我最近在Jetpack Compose项目里遇到了一个很奇怪的自动跳转问题,折腾了一阵还是没搞清楚根因,想请教下社区的大佬们。

问题现象

  • 首次打开应用:进入引导页(Onboarding),点击登录按钮进入登录页,输入凭证完成登录后进入主流程
  • 登出流程:进入设置页点击登出,成功导航回引导页
  • 异常触发:再次点击引导页的登录按钮,直接跳过登录页的凭证输入步骤,自动跳转到首页

我的架构与怀疑点

项目里用了一个NavigationRoot根Composable统一处理导航逻辑,负责给各个页面注入ViewModel、传递状态和回调函数。目前我有两个核心怀疑:

  1. ViewModel生命周期与根Composable绑定:LoginViewModel是在NavigationRoot中通过Koin注入的,而根Composable的生命周期和应用一致,不会随页面回栈销毁。当用户再次进入登录页时,存活的ViewModel可能还保留着之前登录成功的状态,自动触发了跳转到首页的逻辑
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 10:58:05