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

Jetpack Compose中NavHostController导航函数空指针异常求助

Jetpack Compose + FirebaseAuth 重启后登录跳转空指针问题

我在Jetpack Compose应用中用FirebaseAuth实现了Google一键登录功能,成功登录后关闭再重启应用,会因checkAuthState()函数触发NullPointerException,该函数用于在用户已认证时跳转至个人资料页。

错误日志

FATAL EXCEPTION: main
Process: com.codrutursache.casey, PID: 28428
java.lang.NullPointerException
    at androidx.navigation.NavController.navigate(NavController.kt:1652)
    at androidx.navigation.NavController.navigate(NavController.kt:1984)
    at androidx.navigation.NavController.navigate$default(NavController.kt:1979)
    at com.codrutursache.casey.presentation.MainActivity.navigateToProfileScreen(MainActivity.kt:78)
    at com.codrutursache.casey.presentation.MainActivity.checkAuthState(MainActivity.kt:74)
    at com.codrutursache.casey.presentation.MainActivity.access$checkAuthState(MainActivity.kt:33)
    at com.codrutursache.casey.presentation.MainActivity$onCreate$1.invoke(MainActivity.kt:68)
    at com.codrutursache.casey.presentation.MainActivity$onCreate$1.invoke(MainActivity.kt:42)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:428)
    at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:252)
    at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:251)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:195)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:119)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:118)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:110)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(Wrapper.android.kt:158)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$2.invoke(Wrapper.android.kt:157)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:157)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:142)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:108)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:35)
    at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:78)
    at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3340)
    at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3273)
    at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:588)
    at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1013)
    at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:520)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:142)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:133)
2024-01-08 18:13:08.263 28428-28428 AndroidRuntime          com.codrutursache.casey              E      at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1191)
    at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:133)
    at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:183)
    at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.kt:314)
    at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.kt:192)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:140)
    at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:133)
    at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1266)
    at android.view.View.dispatchAttachedToWindow(View.java:21980)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3490)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
    at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3497)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3014)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2465)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9305)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1339)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1348)
    at android.view.Choreographer.doCallbacks(Choreographer.java:952)
    at android.view.Choreographer.doFrame(Choreographer.java:882)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1322)
    at android.os.Handler.handleCallback(Handler.java:958)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8177)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

原MainActivity代码

@AndroidEntryPoint
@ExperimentalAnimationApi
class MainActivity : ComponentActivity() {

    private lateinit var navController: NavHostController
    private lateinit var authViewModel: AuthViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            navController = rememberNavController()
            authViewModel = hiltViewModel<AuthViewModel>()

            val isBottomBarVisible =
                rememberSaveable { mutableStateOf(false) }

            CaseyTheme {
                isBottomBarVisible.value = when (currentRoute(navController)) {
                    Route.AuthRoute.route -> false
                    Route.ProfileRoute.route -> true
                    else -> true
                }
                Scaffold(
                    bottomBar = {
                        if (isBottomBarVisible.value)
                            BottomBar(navController = navController)
                    }
                ) { paddingValues ->
                    NavGraph(
                        navController = navController,
                        modifier = Modifier.padding(paddingValues)
                    )
                }
            }

            checkAuthState()
        }
    }

    private fun checkAuthState() {
        if (authViewModel.isUserAuthenticated) {
            navigateToProfileScreen()
        }
    }

    private fun navigateToProfileScreen() = navController.navigate("profile_screen")

}

@Composable
fun currentRoute(navController: NavController): String? {
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    return navBackStackEntry?.destination?.route
}

问题分析

  1. NavController初始化时机问题:navController是在Composable作用域内通过rememberNavController()创建的,但checkAuthState()是同步调用的,此时NavController还未完成与NavGraph的绑定,调用navigate会触发空指针。
  2. 认证状态判断时机错误:直接在onCreate中一次性判断isUserAuthenticated,没有考虑Compose组件的初始化周期,也没有监听状态变化。

修复方案

将导航逻辑移到Composable内部,使用LaunchedEffect确保在NavController准备完成后再执行导航,同时监听认证状态的变化:

修改后的代码

@AndroidEntryPoint
@ExperimentalAnimationApi
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val navController = rememberNavController()
            val authViewModel = hiltViewModel<AuthViewModel>()

            val isBottomBarVisible =
                rememberSaveable { mutableStateOf(false) }

            CaseyTheme {
                isBottomBarVisible.value = when (currentRoute(navController)) {
                    Route.AuthRoute.route -> false
                    Route.ProfileRoute.route -> true
                    else -> true
                }
                Scaffold(
                    bottomBar = {
                        if (isBottomBarVisible.value)
                            BottomBar(navController = navController)
                    }
                ) { paddingValues ->
                    NavGraph(
                        navController = navController,
                        modifier = Modifier.padding(paddingValues)
                    )
                }

                // 监听认证状态,确保NavController已初始化完成后再导航
                LaunchedEffect(authViewModel.isUserAuthenticated) {
                    if (authViewModel.isUserAuthenticated) {
                        // 避免重复导航到同一页面
                        val currentRoute = navController.currentBackStackEntry?.destination?.route
                        if (currentRoute != Route.ProfileRoute.route) {
                            navController.navigate(Route.ProfileRoute.route) {
                                // 清除登录页的返回栈,防止用户返回登录界面
                                popUpTo(Route.AuthRoute.route) { inclusive = true }
                            }
                        }
                    }
                }
            }
        }
    }
}

@Composable
fun currentRoute(navController: NavController): String? {
    val navBackStackEntry by navController.currentBackStackEntryAsState()
    return navBackStackEntry?.destination?.route
}

额外优化(如果isUserAuthenticated是Flow类型)

如果你的ViewModel中用Flow来暴露认证状态,应该用collect持续监听:

LaunchedEffect(Unit) {
    authViewModel.isUserAuthenticated.collect { isAuthenticated ->
        if (isAuthenticated) {
            val currentRoute = navController.currentBackStackEntry?.destination?.route
            if (currentRoute != Route.ProfileRoute.route) {
                navController.navigate(Route.ProfileRoute.route) {
                    popUpTo(Route.AuthRoute.route) { inclusive = true }
                }
            }
        }
    }
}

关键修改点

  • 移除了Activity级别的lateinit var navController和authViewModel,直接在Composable中获取,符合Compose的状态管理规范。
  • 使用LaunchedEffect触发导航,确保NavController已经和NavGraph绑定完成,避免空指针。
  • 添加路由检查,防止重复导航导致的异常。
  • 配置popUpTo清除登录页返回栈,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 16:37:00