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
相关产品推荐
相关产品推荐

