I'm implementing phone number authentication in my Android app using Firebase. I'm able to initiate the verification process, and the onCodeSent() callback is triggered in my UserRepositoryImpl class, but the SMS message with the OTP never arrives on the device.
I've double-checked the following:
The phone number I'm using is one of the test numbers listed in the Firebase console under "Phone Numbers for Testing". I'm using the exact format (including the country code and "+" prefix) and case (uppercase/lowercase) as it appears in the console. My device can receive other SMS messages.
Here are the relevant code snippets:
PhoneSignUpViewModel.kt:
fun onSignUpClick(activity: Activity, phoneNumber: String) {
viewModelScope.launch {
isLoading = true
// LOG: Check the number before sending to the repository
Log.d("PhoneSignUpViewModel", "Phone number being sent to signUp: $phoneNumber")
userRepository.signUp(phoneNumber) // No need to pass token here
.collect { result ->
when (result) {
is Result.Success -> {
// Update the shared state holder (verificationId and token are in result.data)
otpSharedStateHolder.otpVerificationData.value =
OtpVerificationData(
result.data.verificationId ?: "",
result.data.token
)
isLoading = false
_events.emit(Event.NavigateToOtpVerification)
}
is Result.Error -> {
showError = true
isLoading = false
}
is Result.Loading -> {
isLoading = true
}
}
}
}
}
UserRepositoryImpl.kt:
override fun signUp(
phoneNumber: String,
token: PhoneAuthProvider.ForceResendingToken?
): Flow<Result<User>> = callbackFlow {
trySend(Result.Loading)
launch {
try {
// LOG: Verify the number format received by the repository
Log.d("UserRepositoryImpl", "Phone number received in signUp: $phoneNumber")
val optionsBuilder = PhoneAuthOptions.newBuilder(firebaseAuth)
.setPhoneNumber(phoneNumber)
.setTimeout(60L, TimeUnit.SECONDS)
.setCallbacks(object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
// (Unlikely to happen for phone auth)
launch {
try {
firebaseAuth.signInWithCredential(credential).await()
val user = firebaseAuth.currentUser
trySend(
Result.Success(
User(
phoneNumber = phoneNumber,
id = user?.uid
)
)
)
} catch (e: Exception) {
trySend(
Result.Error(
Exception("Sign-in failed: ${e.message}")
)
)
}
}
}
override fun onVerificationFailed(e: FirebaseException) {
Log.e(
"UserRepositoryImpl",
"onVerificationFailed: ${e.message}",
e
)
trySend(Result.Error(e))
}
override fun onCodeSent(
verificationId: String,
forceResendingToken: PhoneAuthProvider.ForceResendingToken
) {
Log.d("UserRepositoryImpl", "onCodeSent: $verificationId")
trySend(
Result.Success(
User(
phoneNumber = phoneNumber,
verificationId = verificationId,
token = forceResendingToken
)
)
)
close() // Close the callbackFlow
}
})
if (token != null) {
optionsBuilder.setForceResendingToken(token)
}
val options = optionsBuilder.build()
PhoneAuthProvider.verifyPhoneNumber(options)
} catch (e: Exception) {
trySend(Result.Error(e))
}
}
awaitClose { /* Clean up resources if needed */ }
}.catch { emit(Result.Error(it as Exception)) }
Logcat Outputs :
- D/PhoneSignUpViewModel: Phone number being sent to signUp: +16505550100
- D/UserRepositoryImpl: Phone number received in signUp: +16505550100
- D/UserRepositoryImpl: onCodeSent: [VERIFICATION_ID]
What I've Tried: I've carefully followed the Firebase documentation for phone number authentication (confused about playintegrity and appcheck). Added the manage debug token as well in firebase.
Tech Stack
Kotlin
Jetpack Compose
Hilt
Firebase Authentication
Play Integrity API(did it but not so clear about it)