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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 15:25:44