Jetpack Compose中Firebase Auth登录时协程冻结问题
解决Jetpack Compose中Firebase Auth登录协程冻结及状态丢失问题
一、解决应用冻结问题
问题原因
在Compose的onClick主线程回调中使用runBlocking会直接阻塞主线程,导致UI无法响应。runBlocking会挂起当前线程直到协程执行完毕,而主线程被阻塞后无法处理任何UI事件,最终表现为应用冻结。
修复方案
使用rememberCoroutineScope创建与Composable生命周期绑定的协程作用域,在点击事件中启动协程执行登录逻辑,避免阻塞主线程。
修改后的LoginScreen代码:
@OptIn(ExperimentalMaterial3Api::class) @Composable fun LoginScreen(navController: NavController) { val auth = FirebaseAuth.getInstance() val coroutineScope = rememberCoroutineScope() val userState = // 你的用户状态变量 val passwordState = // 你的密码状态变量 // 仅在页面首次加载时执行一次登出操作 LaunchedEffect(Unit) { auth.signOut() } // 省略中间UI代码 Button( onClick = { val email = userState.toString() val password = passwordState.toString() if (email.isNotEmpty() && password.isNotEmpty()) { coroutineScope.launch { try { signInUser(auth, email, password) // 登录成功直接跳转,无需额外判断currentUser navController.navigate("HomeScreen/$email/$password") } catch (e: Exception) { // 处理登录失败,比如提示用户错误信息 // Toast.makeText(LocalContext.current, e.message, Toast.LENGTH_SHORT).show() } } } } ) { Text("登录") } }
保留原signInUser函数,建议配合异常处理使用:
suspend fun signInUser(auth: FirebaseAuth, email: String, password: String) { auth.signInWithEmailAndPassword(email, password).await() }
二、解决登录状态页面间丢失问题
问题原因
- 原代码中
auth.signOut()写在Composable根作用域内,每次Compose重组都会执行该操作,强制清除登录状态。 - 未正确绑定Firebase Auth状态监听与Compose生命周期,导致状态更新无法及时同步到页面。
修复方案
- 仅在登录页面首次加载时执行
signOut,通过LaunchedEffect(Unit)实现(已包含在上述代码中)。 - 使用
AuthStateListener监听登录状态变化,并通过DisposableEffect管理监听的生命周期,避免内存泄漏:
在主导航所在的Composable中添加状态监听逻辑:
@Composable fun AuthStateHandler(navController: NavController) { val auth = FirebaseAuth.getInstance() // 缓存监听实例,避免每次重组重复创建 val authStateListener = remember { FirebaseAuth.AuthStateListener { firebaseAuth -> val currentUser = firebaseAuth.currentUser if (currentUser == null) { // 用户未登录,跳转至登录页并清空栈 navController.navigate("LoginScreen") { popUpTo(navController.graph.startDestinationId) { inclusive = true } } } else { // 用户已登录,确保停留在主页 if (navController.currentDestination?.route?.startsWith("HomeScreen") != true) { navController.navigate("HomeScreen/${currentUser.email}/***") { popUpTo(navController.graph.startDestinationId) { inclusive = true } } } } } } // 在Composable生命周期内添加/移除监听 DisposableEffect(auth) { auth.addAuthStateListener(authStateListener) onDispose { auth.removeAuthStateListener(authStateListener) } } }
之后在你的NavHost所在的根Composable中调用AuthStateHandler(navController),即可自动同步登录状态并处理页面跳转。
三、依赖版本一致性调整
将协程相关依赖版本统一,避免版本冲突:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3")
额外注意事项
- 不要在Composable根作用域执行会修改全局状态的操作(如
signOut),除非明确需要每次重组都执行。 - 登录成功后无需手动判断
auth.currentUser,因为await()会在登录成功后返回,失败则直接抛出异常,可通过try-catch块处理失败场景。 - 建议捕获Firebase Auth的具体异常类型(如
FirebaseAuthInvalidCredentialsException),给用户更精准的错误提示。
内容的提问来源于stack exchange,提问作者rutledge
相关产品推荐
相关产品推荐

