如何在Jetpack Compose中使用Firebase实现用户认证?
在Jetpack Compose中实现Firebase注册功能
一、先修复UI层的状态管理问题
你当前的代码存在明显的状态绑定错误:所有输入框都复用了lastName状态,需要为每个输入项单独定义可观察状态:
@OptIn(ExperimentalMaterial3Api::class) @Composable fun RegistrationForUsers( onSignUpSuccess: () -> Unit, viewModel: AuthViewModel = viewModel() ) { // 为每个输入项单独定义状态 val emailState = remember { mutableStateOf("") } val passwordState = remember { mutableStateOf("") } val confirmPasswordState = remember { mutableStateOf("") } val errorMessage = remember { mutableStateOf("") } // 收集ViewModel的认证状态 val authState by viewModel.authState.collectAsStateWithLifecycle() // 处理认证状态回调 LaunchedEffect(authState) { when (authState) { is AuthViewModel.AuthState.Success -> onSignUpSuccess() is AuthViewModel.AuthState.Error -> { errorMessage.value = (authState as AuthViewModel.AuthState.Error).message } else -> {} } } // 原有布局代码,替换状态绑定 Box( modifier = Modifier .fillMaxSize() .padding(30.dp) ) { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.Start ) { Text( text = "Sign Up", color = LightBlue, style = TextStyle( fontSize = 40.sp, fontFamily = FontFamily.SansSerif, fontWeight = FontWeight.Bold, ) ) Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.Start, ) { Spacer(modifier = Modifier.height(40.dp)) // 邮箱输入框 Spacer(modifier = Modifier.padding(top = 15.dp)) OutlinedTextField( value = emailState.value, onValueChange = { emailState.value = it }, label = { Text(text = "E-mail") }, singleLine = true, leadingIcon = { Icon( imageVector = Icons.Default.Email, contentDescription = "邮箱图标" ) }, modifier = Modifier.fillMaxWidth(), isError = errorMessage.value.isNotEmpty() && !android.util.Patterns.EMAIL_ADDRESS.matcher(emailState.value).matches() ) // 密码输入框 Spacer(modifier = Modifier.padding(top = 15.dp)) OutlinedTextField( value = passwordState.value, onValueChange = { passwordState.value = it }, label = { Text(text = "Password") }, singleLine = true, leadingIcon = { Icon( imageVector = Icons.Default.Lock, contentDescription = "密码图标" ) }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) ) // 确认密码输入框 Spacer(modifier = Modifier.padding(top = 15.dp)) OutlinedTextField( value = confirmPasswordState.value, onValueChange = { confirmPasswordState.value = it }, label = { Text(text = "Confirm Password") }, singleLine = true, leadingIcon = { Icon( imageVector = Icons.Default.Lock, contentDescription = "确认密码图标" ) }, modifier = Modifier.fillMaxWidth(), visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password) ) // 显示错误信息 if (errorMessage.value.isNotEmpty()) { Text( text = errorMessage.value, color = Color.Red, modifier = Modifier.padding(top = 8.dp) ) } // 注册按钮 val isLoading = authState !is AuthViewModel.AuthState.Idle Spacer(modifier = Modifier.height(60.dp)) Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) { Button( onClick = { // 先做本地验证 if (!android.util.Patterns.EMAIL_ADDRESS.matcher(emailState.value).matches()) { errorMessage.value = "请输入有效的邮箱地址" return@Button } if (passwordState.value.length < 6) { errorMessage.value = "密码长度不能少于6位" return@Button } if (passwordState.value != confirmPasswordState.value) { errorMessage.value = "两次输入的密码不一致" return@Button } // 调用ViewModel的注册逻辑 viewModel.signUp(emailState.value, passwordState.value) }, shape = RoundedCornerShape(50.dp), modifier = Modifier .fillMaxWidth() .height(50.dp), enabled = !isLoading ) { if (isLoading) { CircularProgressIndicator(modifier = Modifier.size(20.dp), color = Color.White) Spacer(modifier = Modifier.width(8.dp)) } Text(text = "Sign Up", fontSize = 17.sp, fontWeight = FontWeight.Bold) } } Spacer(modifier = Modifier.height(10.dp)) ClickableText( text = AnnotatedString("Already have an account?"), onClick = { onSignUpSuccess() }, modifier = Modifier.wrapContentSize(), style = TextStyle( fontSize = 13.sp, fontFamily = FontFamily.Default, ), ) } } } }
二、分离业务逻辑到ViewModel
Compose中推荐将业务逻辑放在ViewModel中,避免UI层代码臃肿:
class AuthViewModel : ViewModel() { private val auth = FirebaseAuth.getInstance() private val _authState = MutableStateFlow<AuthState>(AuthState.Idle) val authState: StateFlow<AuthState> = _authState fun signUp(email: String, password: String) { _authState.value = AuthState.Loading auth.createUserWithEmailAndPassword(email, password) .addOnCompleteListener { task -> if (task.isSuccessful) { _authState.value = AuthState.Success } else { _authState.value = AuthState.Error(task.exception?.message ?: "注册失败,请重试") } } } sealed class AuthState { object Idle : AuthState() object Loading : AuthState() object Success : AuthState() data class Error(val message: String) : AuthState() } }
三、页面跳转处理
在调用RegistrationForUsers的地方,传入注册成功后的跳转回调(以Navigation Compose为例):
NavHost(navController = navController, startDestination = "signup") { composable("signup") { RegistrationForUsers( onSignUpSuccess = { navController.navigate("login") { popUpTo("signup") { inclusive = true } // 从返回栈移除注册页 } } ) } composable("login") { LoginScreen() // 你的登录页Composable } }
内容的提问来源于stack exchange,提问作者Josef M
相关产品推荐
相关产品推荐

