Jetpack Compose重发Firebase验证码报resendToken未初始化错误
问题描述
基于Android Jetpack Compose开发Firebase手机号认证模块时,已实现首次OTP验证码下发功能,在开发验证码重发功能时,为「Resend Code」控件添加如下点击逻辑:
.clickable { modelAuthentication.resendCode(phoneNumberOTP)}
点击控件后程序抛出lateinit property resendToken has not been initialized异常,核心业务代码如下:
核心代码片段
Phone.kt(手机号输入页)
@Composable fun PhoneScreen( navController: NavController, modelAuthentication: AuthenticationViewModel ) { val phoneNumberState = remember { mutableStateOf("") } OutlinedTextField( value = phoneNumberState.value, colors = TextFieldDefaults.textFieldColors( backgroundColor = white, focusedIndicatorColor = Grey, unfocusedIndicatorColor = Grey, focusedLabelColor = Grey, unfocusedLabelColor = Grey, cursorColor = color, textColor = color, ), onValueChange = { phoneNumberState.value = it }, label = { Text(text = "Phone Number") }, placeholder = { Text(text = "Phone Number") }, singleLine = true, ) Button( modifier = Modifier .width(285.dp) .height(55.dp), onClick = { modelAuthentication.send(phoneNumberState.value) }, colors = ButtonDefaults.buttonColors( backgroundColor = color ), shape = RoundedCornerShape(40), ) { Text( text = "send", style = TextStyle( fontSize = 18.sp, color = white, ) ) } }
PhoneVerify.kt(验证码校验页)
@Composable fun PhoneVerifyScreen( navController: NavController, modelAuthentication: AuthenticationViewModel, ) { // 冗余无效变量,从未被赋值使用 lateinit var resendToken: PhoneAuthProvider.ForceResendingToken val focusManager = LocalFocusManager.current val phoneNumberOTP = remember { mutableStateOf("") } val context = LocalContext.current LaunchedEffect(Unit) { println("found activity? ${context.findActivity()}") val activity = context.findActivity() ?: return@LaunchedEffect modelAuthentication.setActivity(activity) } Column( Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { OutlinedTextField( value = phoneNumberOTP.value, colors = TextFieldDefaults.textFieldColors( backgroundColor = white, focusedIndicatorColor = Grey, unfocusedIndicatorColor = Grey, focusedLabelColor = Grey, unfocusedLabelColor = Grey, cursorColor = color, textColor = color, ), onValueChange = { phoneNumberOTP.value = it }, label = { Text(text = "Verify code") }, placeholder = { Text(text = "Verify code") }, modifier = Modifier.fillMaxWidth(0.8f), ) Spacer(modifier = Modifier.padding(7.dp)) Row( Modifier.width(300.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { Text( "Resend Code", modifier = Modifier .height(20.dp) .clickable { // 传参错误:传入的是验证码输入值,不是目标手机号 modelAuthentication.resendCode(phoneNumberOTP) }, textAlign = TextAlign.End, fontWeight = FontWeight.Medium, fontSize = 14.sp, color = color ) } } }
AuthenticationViewModel.kt(认证逻辑ViewModel)
@HiltViewModel class AuthenticationViewModel @Inject constructor( private val auth: FirebaseAuth ) : ViewModel() { lateinit var otp: String var verificationOtp = "" var popNotification = mutableStateOf<Event<String>?>(null) private lateinit var baseBuilder: PhoneAuthOptions.Builder fun setActivity(activity: Activity) { baseBuilder = PhoneAuthOptions.newBuilder(auth).setActivity(activity) } lateinit var resendToken: PhoneAuthProvider.ForceResendingToken val mCallBack = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { override fun onVerificationCompleted(p0: PhoneAuthCredential) { handledException(customMessage = "Verification Completed") } override fun onVerificationFailed(p0: FirebaseException) { handledException(customMessage = "Verification Failed") } override fun onCodeSent(otp: String, p1: PhoneAuthProvider.ForceResendingToken) { super.onCodeSent(otp, p1) verificationOtp = otp resendToken = p1 handledException(customMessage = "Otp Send Successfully") } } fun sendVerificationCode(mobileNum: String) { val options = baseBuilder .setPhoneNumber(mobileNum) .setTimeout(5L, TimeUnit.SECONDS) .setCallbacks(mCallBack) .build() PhoneAuthProvider.verifyPhoneNumber(options) } fun signInWithPhoneAuthCredential(otp: String) { val credential = PhoneAuthProvider.getCredential(verificationOtp, otp) auth.currentUser?.linkWithCredential(credential) FirebaseAuth.getInstance().signInWithCredential(credential) .addOnCompleteListener { task -> if (task.isSuccessful) { handledException(customMessage = "Verification Successful") } else { handledException(customMessage = "Wrong Otp") } } } private fun handledException(exception: Exception? = null, customMessage: String = "") { exception?.printStackTrace() val errorMsg = exception?.message ?: "" val message = if (customMessage.isEmpty()) { errorMsg } else { "$customMessage: $errorMsg" } popNotification.value = Event(message) } // 缺失resendCode方法实现 }
问题根因
- 核心逻辑缺失:ViewModel中未实现
resendCode方法,重发验证码的逻辑完全没有编写 - lateinit变量时序风险:
resendToken仅在首次发送验证码触发onCodeSent回调时才会被赋值,若回调未执行完成就点击重发按钮,直接访问未初始化的lateinit变量就会抛出异常 - 传参错误:重发验证码需要传入目标手机号,代码中传入的是验证码输入框的状态对象
phoneNumberOTP,参数类型和值完全不匹配 - 冗余代码干扰:PhoneVerifyScreen中定义的局部
resendToken变量从未被赋值、从未参与业务逻辑,属于无效代码,容易引发变量混淆
修复方案
- 改造ViewModel代码:将
resendToken改为可空类型规避lateinit初始化异常,补充resendCode方法实现,增加token有效性校验,同时存储用户输入的手机号用于重发
修复后的ViewModel核心代码:
@HiltViewModel class AuthenticationViewModel @Inject constructor( private val auth: FirebaseAuth ) : ViewModel() { // 存储用户输入的手机号,供重发使用 private var currentPhoneNumber: String = "" lateinit var otp: String var verificationOtp = "" var popNotification = mutableStateOf<Event<String>?>(null) private lateinit var baseBuilder: PhoneAuthOptions.Builder fun setActivity(activity: Activity) { baseBuilder = PhoneAuthOptions.newBuilder(auth).setActivity(activity) } // 改为可空类型,避免lateinit未初始化崩溃 private var resendToken: PhoneAuthProvider.ForceResendingToken? = null val mCallBack = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { override fun onVerificationCompleted(p0: PhoneAuthCredential) { handledException(customMessage = "Verification Completed") } override fun onVerificationFailed(p0: FirebaseException) { handledException(customMessage = "Verification Failed") } override fun onCodeSent(otp: String, p1: PhoneAuthProvider.ForceResendingToken) { super.onCodeSent(otp, p1) verificationOtp = otp resendToken = p1 handledException(customMessage = "Otp Send Successfully") } } // 对齐Compose里调用的send方法,实际触发发送逻辑并存入手机号 fun send(mobileNum: String) { currentPhoneNumber = mobileNum sendVerificationCode(mobileNum) } fun sendVerificationCode(mobileNum: String) { val options = baseBuilder .setPhoneNumber(mobileNum) .setTimeout(5L, TimeUnit.SECONDS) .setCallbacks(mCallBack) .build() PhoneAuthProvider.verifyPhoneNumber(options) } // 补充重发逻辑 fun resendCode() { val token = resendToken if (token == null) { handledException(customMessage = "Please wait for the first verification code to be sent") return } val options = baseBuilder .setPhoneNumber(currentPhoneNumber) .setTimeout(5L, TimeUnit.SECONDS) .setCallbacks(mCallBack) .setForceResendingToken(token) // 传入重发token .build() PhoneAuthProvider.verifyPhoneNumber(options) } // 其余signInWithPhoneAuthCredential、handledException方法保持原有逻辑不变 }
- 修复Compose页面代码:
- 删除PhoneVerifyScreen中冗余的局部
resendToken变量 - 修正重发按钮的点击调用,不需要传参,直接调用
modelAuthentication.resendCode() - 可增加重发冷却逻辑,首次发送验证码后60秒内禁用重发按钮,避免用户频繁点击触发接口拦截
- 路由参数对齐:如果手机号是通过导航从PhoneScreen传到PhoneVerifyScreen的,要确保ViewModel中存储的
currentPhoneNumber和导航传递的值一致,避免重发时手机号错误。
内容的提问来源于stack exchange,提问作者Huma33
相关产品推荐
相关产品推荐

