I have phone authentication project in android jetpack compose, so I can do it in my code and I am success to get OTP code from firebase, but I want to use resend code again in my project, so when I click this line of code;
.clickable { modelAuthentication.resendCode(phoneNumberOTP)}
it is throw an error like "lateinit property resendToken has not been initialized", I am not get it what I missed, any idea?
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 phoneNumberPatientOTP = 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
)
} }
ViewModel.kt
@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)
}
}
CodePudding user response:
Modify your viewmodel looks like this
1 Define the callback on the top
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")
}}
2 Update the send function
fun sendVerificationCode(mobileNum: String) {
val options = baseBuilder
.setPhoneNumber(mobileNum)
.setTimeout(5L, TimeUnit.SECONDS)
.setCallbacks(mCallBack)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
3 Remove the resend function
Note: p1 parameter is the Resend token
If you want resend verification, call the same function (sendVerificationCode)
CodePudding user response:
This, in your ViewModel
lateinit var resendToken: PhoneAuthProvider.ForceResendingToken
is a variable that hasn't been initialised to a value. You're not usually allowed to do that - you have to initialise it with something - but the lateinit
keyword allows you to initialise it later. You're making a promise that you'll set a value on it before something tries to read that value. The error message you're getting, that resendToken has not been initialized
means you've broken that promise!
You're initialising it in one of your callbacks:
override fun onCodeSent(
verificationId: String,
token: PhoneAuthProvider.ForceResendingToken,
) {
...
resendToken = token
}
but the problem is this is running after you've tried to use resendToken
in the resendCode
function, which you call when you click the button. You need a value for resendToken
before you can do this:
.setForceResendingToken(resendToken)
That's just a general explanation of the problem - I haven't used the Firebase stuff you're working with, so I can't tell you the best way to fix it. But you're calling that PhoneAuthOptions
builder with a token you don't have yet, which is returned by the callbacks
you're also providing to that builder. To me, that implies you have to be able to do the verifyPhoneNumber
call without a token, since that's the only way you get one?
So maybe make resendToken
nullable instead of lateinit
, and check if it's null before adding it to the builder:
var resendToken: PhoneAuthProvider.ForceResendingToken? = null
fun resendCode(mobileNum: MutableState<String>) {
val options = PhoneAuthOptions.newBuilder(auth).apply {
setPhoneNumber(mobileNum.toString())
setTimeout(60L, TimeUnit.SECONDS)
setCallbacks(callbacks)
// only set this on the builder if we have it!
resendToken?.let { setForceResendingToken(it) }
}.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
(I'm assuming the builder methods return the same Builder
instance so we can just call apply
on it and the code is neater.) I don't know if that actually works, but I'm just giving you ideas for stuff to try