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 }
问题分析
- NavController初始化时机问题:
navController是在Composable作用域内通过rememberNavController()创建的,但checkAuthState()是同步调用的,此时NavController还未完成与NavGraph的绑定,调用navigate会触发空指针。 - 认证状态判断时机错误:直接在
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
相关产品推荐
相关产品推荐

