Unable to get OTP from sms broadcast

Viewed 439

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) 
0 Answers
Related