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

MVVM架构下Android Firebase电话认证setActivity适配方案问询

在MVVM架构中实现Firebase电话认证的正确方式

要在不破坏MVVM架构的前提下将Firebase电话认证逻辑移到ViewModel中,核心是避免ViewModel持有Activity/Fragment的强引用,同时满足Firebase对生命周期绑定的需求。以下是具体实现方案:

核心思路

Firebase的PhoneAuthOptions支持通过setLifecycleOwner()替代setActivity()来绑定回调的生命周期,这样我们可以让UI层(Activity/Fragment)临时传递自身作为LifecycleOwner给ViewModel的方法,而ViewModel不会持有长期引用,完全符合MVVM的职责分离原则。认证结果通过LiveData通知UI层,UI层只负责状态展示和用户交互。

1. ViewModel实现

封装电话认证逻辑,用密封类定义认证状态,通过LiveData将状态传递给UI层:

class PhoneAuthViewModel(private val auth: FirebaseAuth) : ViewModel() {
    // 内部可变LiveData,对外暴露不可变版本
    private val _authState = MutableLiveData<AuthState>()
    val authState: LiveData<AuthState> = _authState

    // 密封类定义所有可能的认证状态
    sealed class AuthState {
        object CodeSent : AuthState()
        data class VerificationCompleted(val credential: PhoneAuthCredential) : AuthState()
        data class Error(val message: String) : AuthState()
    }

    // 启动电话验证,临时接收LifecycleOwner
    fun startPhoneVerification(phoneNumber: String, lifecycleOwner: LifecycleOwner) {
        val callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
            override fun onVerificationCompleted(credential: PhoneAuthCredential) {
                _authState.postValue(AuthState.VerificationCompleted(credential))
            }

            override fun onVerificationFailed(e: FirebaseException) {
                _authState.postValue(AuthState.Error(e.message ?: "验证失败,请重试"))
            }

            override fun onCodeSent(
                verificationId: String,
                token: PhoneAuthProvider.ForceResendingToken
            ) {
                // 可保存verificationId和token,用于后续重发验证码
                _authState.postValue(AuthState.CodeSent)
            }
        }

        val options = PhoneAuthOptions.newBuilder(auth)
            .setPhoneNumber(phoneNumber)
            .setTimeout(60L, TimeUnit.SECONDS)
            .setLifecycleOwner(lifecycleOwner) // 绑定到UI层的生命周期
            .setCallbacks(callbacks)
            .build()

        PhoneAuthProvider.verifyPhoneNumber(options)
    }
}

2. UI层(Activity/Fragment)实现

负责触发认证操作、观察ViewModel的状态更新,并处理UI反馈:

class PhoneAuthActivity : AppCompatActivity() {
    // 通过ViewModelFactory注入FirebaseAuth依赖
    private val viewModel: PhoneAuthViewModel by viewModels {
        PhoneAuthViewModelFactory(FirebaseAuth.getInstance())
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_phone_auth)

        // 观察认证状态变化
        viewModel.authState.observe(this) { state ->
            when (state) {
                is PhoneAuthViewModel.AuthState.CodeSent -> {
                    // 跳转至验证码输入界面或显示输入框
                    Toast.makeText(this, "验证码已发送", Toast.LENGTH_SHORT).show()
                }
                is PhoneAuthViewModel.AuthState.VerificationCompleted -> {
                    // 自动完成验证,执行登录逻辑
                    signInWithPhoneAuthCredential(state.credential)
                }
                is PhoneAuthViewModel.AuthState.Error -> {
                    // 显示错误信息
                    Toast.makeText(this, state.message, Toast.LENGTH_SHORT).show()
                }
            }
        }

        // 用户点击验证按钮时触发
        btn_send_code.setOnClickListener {
            val phoneNumber = et_phone_input.text.toString().trim()
            if (phoneNumber.isNotEmpty()) {
                viewModel.startPhoneVerification(phoneNumber, this) // 传递当前Activity作为LifecycleOwner
            }
        }
    }

    // 登录逻辑可放在UI层或封装到ViewModel中(建议后者)
    private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {
        viewModel.signInWithCredential(credential)
    }
}

3. ViewModelFactory(可选但推荐)

用于给ViewModel注入FirebaseAuth依赖,方便单元测试:

class PhoneAuthViewModelFactory(private val auth: FirebaseAuth) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(PhoneAuthViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return PhoneAuthViewModel(auth) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

为什么符合MVVM架构

  • 职责分离:ViewModel封装所有认证业务逻辑,UI层只处理用户交互和状态展示,不涉及业务逻辑。
  • 无内存泄漏风险:ViewModel仅在方法调用时临时使用LifecycleOwner,不持有长期引用;setLifecycleOwner()会自动在UI组件销毁时取消Firebase回调。
  • 可测试性:依赖注入FirebaseAuth,便于在单元测试中替换为Mock实例。

内容的提问来源于stack exchange,提问作者J P

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 23:32:01