Android Jetpack Compose中Firebase用户已存在时添加Snackbar的方法
问题解决:Firebase注册用户已存在时显示Snackbar
核心概念解释
- Firebase Auth失败回调:调用
createUserWithEmailAndPassword注册失败时,addOnFailureListener会接收FirebaseAuthException,其中用户已存在对应的错误码为ERROR_EMAIL_ALREADY_IN_USE,可通过exception.errorCode获取。 - Compose Snackbar正确用法:Snackbar属于UI层组件,ViewModel不应直接持有
SnackbarHostState(会造成UI与业务逻辑耦合)。正确方式是ViewModel通过状态变量传递提示信息,UI层监听状态并触发Snackbar显示。 - ViewModel协程作用域:使用
viewModelScope(AndroidX Lifecycle库提供)处理协程操作,它会自动绑定ViewModel生命周期,避免内存泄漏。
步骤1:修改SignupViewModel
添加状态变量存储提示信息,完善错误判断逻辑:
package com.example.bettehomes.data.signup import android.util.Log import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseAuthException import com.example.bettehomes.data.RegistrationUIState import com.example.bettehomes.data.rules.Validator import com.example.bettehomes.navigation.BetteHomesAppRouter import com.example.bettehomes.navigation.Screens class SignupViewModel : ViewModel() { private val TAG = SignupViewModel::class.simpleName var registrationUIState = mutableStateOf(RegistrationUIState()) var allValidationsPassed = mutableStateOf(false) var signUpInProgress = mutableStateOf(false) // 新增状态:传递Snackbar提示信息 var snackbarMessage = mutableStateOf<String?>(null) fun onEvent(event: SignupUIEvent) { // 原有代码保持不变 when (event) { is SignupUIEvent.FirstNameChanged -> { registrationUIState.value = registrationUIState.value.copy( firstName = event.firstName ) printState() } is SignupUIEvent.LastNameChanged -> { registrationUIState.value = registrationUIState.value.copy( lastName = event.lastName ) printState() } is SignupUIEvent.EmailChanged -> { registrationUIState.value = registrationUIState.value.copy( email = event.email ) printState() } is SignupUIEvent.PasswordChanged -> { registrationUIState.value = registrationUIState.value.copy( password = event.password ) printState() } is SignupUIEvent.RegisterButtonClicked -> { signUp() } is SignupUIEvent.PrivacyPolicyCheckBoxClicked -> { registrationUIState.value = registrationUIState.value.copy( privacyPolicyAccepted = event.status ) } } validateDataWithRules() } private fun signUp() { Log.d(TAG, "Inside_signUp") printState() createUserInFirebase( email = registrationUIState.value.email, password = registrationUIState.value.password ) } private fun validateDataWithRules() { // 原有代码保持不变 val fNameResult = Validator.validateFirstName( fName = registrationUIState.value.firstName ) val lNameResult = Validator.validateLastName( lName = registrationUIState.value.lastName ) val emailResult = Validator.validateEmail( email = registrationUIState.value.email ) val passwordResult = Validator.validatePassword( password = registrationUIState.value.password ) val privacyPolicyResult = Validator.validatePrivacyPolicyAcceptance( statusValue = registrationUIState.value.privacyPolicyAccepted ) Log.d(TAG, "Inside_validateDataWithRules") Log.d(TAG, "fNameResult= $fNameResult") Log.d(TAG, "lNameResult= $lNameResult") Log.d(TAG, "emailResult= $emailResult") Log.d(TAG, "passwordResult= $passwordResult") Log.d(TAG, "privacyPolicyResult= $privacyPolicyResult") registrationUIState.value = registrationUIState.value.copy( firstNameError = fNameResult.status, lastNameError = lNameResult.status, emailError = emailResult.status, passwordError = passwordResult.status, privacyPolicyError = privacyPolicyResult.status ) allValidationsPassed.value = fNameResult.status && lNameResult.status && emailResult.status && passwordResult.status && privacyPolicyResult.status } private fun printState() { Log.d(TAG, "Inside_printState") Log.d(TAG, registrationUIState.value.toString()) } private fun createUserInFirebase(email: String, password: String) { signUpInProgress.value = true FirebaseAuth .getInstance() .createUserWithEmailAndPassword(email, password) .addOnCompleteListener { Log.d(TAG, "Inside_OnCompleteListener") Log.d(TAG, " isSuccessful = ${it.isSuccessful}") signUpInProgress.value = false if (it.isSuccessful) { BetteHomesAppRouter.navigateTo(Screens.HomeScreen) } } .addOnFailureListener { exception -> Log.d(TAG, "Inside_OnFailureListener") Log.d(TAG, "Exception= ${exception.message}") Log.d(TAG, "Exception= ${exception.localizedMessage}") signUpInProgress.value = false // 判断错误类型,设置对应提示 val errorMessage = if (exception is FirebaseAuthException) { when (exception.errorCode) { "ERROR_EMAIL_ALREADY_IN_USE" -> "该邮箱已被注册,请直接登录" // 可扩展其他错误提示,如密码过短等 else -> "注册失败:${exception.localizedMessage}" } } else { "注册失败:${exception.localizedMessage}" } // 更新状态通知UI层显示Snackbar snackbarMessage.value = errorMessage } } }
步骤2:在Compose UI层处理Snackbar
在注册界面的Composable中,监听ViewModel的提示状态并显示Snackbar:
@Composable fun SignupScreen(viewModel: SignupViewModel = viewModel()) { val snackbarHostState = remember { SnackbarHostState() } // 监听提示状态,触发Snackbar显示 viewModel.snackbarMessage.value?.let { message -> LaunchedEffect(message) { snackbarHostState.showSnackbar(message) // 显示后清空消息,避免重复触发 viewModel.snackbarMessage.value = null } } Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) } ) { paddingValues -> // 注册界面原有UI代码(输入框、按钮等) // ... } }
关键注意点
- 禁止在ViewModel中直接操作
SnackbarHostState,保持ViewModel仅负责业务逻辑与状态管理。 - 使用
LaunchedEffect触发Snackbar,它属于Compose协程作用域,能保证在正确的生命周期内执行。 - Firebase Auth错误码可直接通过字符串判断,无需额外依赖。
内容的提问来源于stack exchange,提问作者Kaleab Woldemariam
相关产品推荐
相关产品推荐

