I created a two screen verification app where in the first screen you enter the Phone Number and in the second screen you need to type the OTP. Android has broadcast service to automatically detect the otp but this is isn't working. I took reference from here and here. My code is:
@Composable
fun VerifyScreen(
navHostController: NavHostController,
viewModel: LoginViewModel = hiltViewModel(),
number: String,
onClick: (otp: String) -> Unit,
) {
val otp = viewModel.otp.value
SmsRetrieverUserConsentBroadcast { _, code -> viewModel.onEvent(UserDataEvent.EnteredOTP(code)) }
MindYugTheme {
Scaffold(
topBar = {
IconButton(
onClick = { navHostController.navigateUp() },
) {
Icon(
imageVector = Icons.Outlined.ArrowBackIos,
contentDescription = "back",
tint = MaterialTheme.colors.secondary
)
}
},
) {
val phoneNumber = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color(0xFFFFFFFF))) {
append("$number ")
}
pushStringAnnotation(
tag = "resend",// provide tag which will then be provided when you click the text
annotation = "resend"
)
withStyle(style = SpanStyle(color = Color(0xFFB5B4ED))) {
append("RESEND")
}
pop()
}
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
modifier = Modifier.align(Alignment.Start),
text = "Verify",
style = Typography.h4
)
Spacer(modifier = Modifier.height(16.dp))
OTPTextFields(
length = 6
) { getOpt ->
otp.text = getOpt
}
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Please enter the OTP sent to:")
// Text(text = number)
ClickableText(text = phoneNumber, onClick = { offset ->
phoneNumber.getStringAnnotations(
tag = "resend",
start = offset,
end = offset
).firstOrNull()?.let {
Log.d("tag", "clicked")
}
})
Spacer(modifier = Modifier.height(24.dp))
GradientButton(onClick = {
navHostController.navigate(Screen.EnterNameScreen.route)
onClick(otp.text)
}) {
// otpVerification(otp, context)
Text(text = "Next")
}
}
}
}
}
@Composable
fun SmsRetrieverUserConsentBroadcast(
smsCodeLength: Int = 6,
onSmsReceived: (message: String, code: String) -> Unit,
) {
val context = LocalContext.current
var shouldRegisterReceiver by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
SmsRetriever.getClient(context)
.startSmsUserConsent(null)
.addOnSuccessListener {
shouldRegisterReceiver = true
}
}
val launcher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it?.resultCode == Activity.RESULT_OK && it.data != null) {
val message: String? = it.data!!.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE)
message?.let {
val verificationCode = getVerificationCodeFromSms(message, smsCodeLength)
onSmsReceived(message, verificationCode)
}
shouldRegisterReceiver = false
} else {
}
}
if (shouldRegisterReceiver) {
SystemBroadcastReceiver(
systemAction = SmsRetriever.SMS_RETRIEVED_ACTION,
broadCastPermission = SmsRetriever.SEND_PERMISSION,
) { intent ->
if (intent != null && SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.get(SmsRetriever.EXTRA_STATUS) as Status
when (smsRetrieverStatus.statusCode) {
CommonStatusCodes.SUCCESS -> {
// Get consent intent
val consentIntent =
extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
try {
// Start activity to show consent dialog to user, activity must be started in
// 5 minutes, otherwise you'll receive another TIMEOUT intent
launcher.launch(consentIntent)
} catch (e: ActivityNotFoundException) {
// Timber.e(e, "Activity Not found for SMS consent API")
}
}
CommonStatusCodes.TIMEOUT -> {
}
}
}
}
}
}
@Composable
fun SystemBroadcastReceiver(
systemAction: String,
broadCastPermission: String,
onSystemEvent: (intent: Intent?) -> Unit
) {
// Grab the current context in this part of the UI tree
val context = LocalContext.current
// Safely use the latest onSystemEvent lambda passed to the function
val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)
// If either context or systemAction changes, unregister and register again
DisposableEffect(context, systemAction) {
val intentFilter = IntentFilter(systemAction)
val broadcast = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
onSystemEvent(intent)
}
}
context.registerReceiver(broadcast, intentFilter)
// When the effect leaves the Composition, remove the callback
onDispose {
context.unregisterReceiver(broadcast)
}
}
DisposableEffect(context, broadCastPermission) {
val intentFilter = IntentFilter(broadCastPermission)
val broadcast = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
onSystemEvent(intent)
}
}
context.registerReceiver(broadcast, intentFilter)
// When the effect leaves the Composition, remove the callback
onDispose {
context.unregisterReceiver(broadcast)
}
}
}
internal fun getVerificationCodeFromSms(sms: String, smsCodeLength: Int): String =
sms.filter { it.isDigit() }
.substring(0 until smsCodeLength)
The error I'm getting is:
java.lang.RuntimeException: Error receiving broadcast Intent { act=com.google.android.gms.auth.api.phone.SMS_RETRIEVED flg=0x200010 pkg=com.mindyug.app (has extras) } in com.google.android.gms.internal.firebase-auth-api.zzvl@f0a6f7b
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1581)
at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:233)
at android.app.ActivityThread.main(ActivityThread.java:8063)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:631)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:978)
Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'int java.lang.CharSequence.length()' on a null object reference
at java.util.regex.Matcher.reset(Matcher.java:256)
at java.util.regex.Matcher.<init>(Matcher.java:167)
at java.util.regex.Pattern.matcher(Pattern.java:1027)
at com.google.android.gms.internal.firebase-auth-api.zzvn.zzb(com.google.firebase:firebase-auth@@21.0.1:1)
at com.google.android.gms.internal.firebase-auth-api.zzvl.onReceive(com.google.firebase:firebase-auth@@21.0.1:8)
at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0$LoadedApk$ReceiverDispatcher$Args(LoadedApk.java:1566)
at android.app.-$$Lambda$LoadedApk$ReceiverDispatcher$Args$_BumDX2UKsnxLVrE6UJsJZkotuA.run(Unknown Source:2)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:233)
at android.app.ActivityThread.main(ActivityThread.java:8063)