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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 02:57:34