如何在Kotlin Jetpack Compose中使用Google SMS Retriever API自动填充OTP
短信验证码自动填充适配Jetpack Compose方案
需求:用户提交信息后跳转至验证码页,接收服务端发送的4位验证码短信,自动填充自定义的4位OTP输入框。现有代码已集成Google SMS Retriever,但未完成Compose下的验证码传递与自动填充逻辑。
步骤1:修改短信广播接收器,提取验证码并传递
原MySMSBroadcastReceiver仅打印短信内容,需添加验证码提取逻辑,并通过本地广播将验证码传递给验证码页的ViewModel:
class MySMSBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) { val extras = intent.extras val status = extras?.get(SmsRetriever.EXTRA_STATUS) as Status when (status.statusCode) { CommonStatusCodes.SUCCESS -> { val message = extras.get(SmsRetriever.EXTRA_SMS_MESSAGE) as String // 提取4位数字验证码 val otpPattern = "\\d{4}".toRegex() val matchResult = otpPattern.find(message) matchResult?.value?.let { otpCode -> // 发送本地广播传递验证码 val localIntent = Intent("OTP_RECEIVED_ACTION").apply { putExtra("OTP_CODE", otpCode) } LocalBroadcastManager.getInstance(context).sendBroadcast(localIntent) } } CommonStatusCodes.TIMEOUT -> { // 处理超时逻辑,比如提示用户重新获取验证码 } } } } }
步骤2:在验证码ViewModel中监听广播,更新OTP状态
修改VerificationPhoneCodeViewModel,添加本地广播监听,接收验证码后更新UI状态:
@HiltViewModel class VerificationPhoneCodeViewModel @Inject constructor( private val context: Context // 通过Hilt注入Context ) : ViewModel() { private val _state = MutableStateFlow(VerificationPhoneCodeScreenState()) val state = _state.asStateFlow() private lateinit var broadcastReceiver: BroadcastReceiver init { registerOtpReceiver() } private fun registerOtpReceiver() { broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { if (intent?.action == "OTP_RECEIVED_ACTION") { val otpCode = intent.getStringExtra("OTP_CODE") ?: return // 更新OTP输入框内容 _state.update { it.copy(otpValue = otpCode) } // 自动触发验证(如果需要) verificationSmsCode(otpCode) } } } // 注册本地广播接收器 LocalBroadcastManager.getInstance(context).registerReceiver( broadcastReceiver, IntentFilter("OTP_RECEIVED_ACTION") ) } override fun onCleared() { super.onCleared() // 销毁时注销广播接收器,避免内存泄漏 LocalBroadcastManager.getInstance(context).unregisterReceiver(broadcastReceiver) } // 保留原有的onChangeOtpValue、verificationSmsCode等方法 fun onChangeOtpValue(value: String) { _state.update { it.copy(otpValue = value) } } fun verificationSmsCode(code: String) { // 原有的验证逻辑 } fun setError(isError: Boolean) { _state.update { it.copy(error = isError) } } }
步骤3:修复OtpTextField的显示问题
原CharView中当index == text.length时显示"0",这会导致输入前每个框都显示0,改为显示空字符串:
@Composable private fun CharView( index: Int, text: String, otpVerificationStatus: OtpVerificationStatus ) { val isFocused = text.length == index val char = when { index < text.length -> text[index].toString() else -> "" // 替换原有的"0"为空 } // 保留原有的颜色逻辑和Text组件 var focusedColor = MaterialTheme.colors.DYTThemeColor var nonFocusedColor = MaterialTheme.colors.grayColor when(otpVerificationStatus){ OtpVerificationStatus.DEFAULT -> { focusedColor = MaterialTheme.colors.DYTThemeColor nonFocusedColor = MaterialTheme.colors.grayColor } OtpVerificationStatus.VERIFICATION_TRUE -> { focusedColor = DYTGreenColor nonFocusedColor = DYTGreenColor } OtpVerificationStatus.VERIFICATION_FALSE -> { focusedColor = red_color nonFocusedColor = red_color } } Text( modifier = Modifier .size(50.dp, 60.dp) .border( 2.dp, when { isFocused -> focusedColor else -> nonFocusedColor }, RoundedCornerShape(8.dp) ) .padding(2.dp) .offset(y = 15.dp), text = char, style = MaterialTheme.typography.h4, color = MaterialTheme.colors.DYTThemeColor, textAlign = TextAlign.Center ) }
步骤4:优化SmsRetriever启动时机
原代码在MainActivity启动时调用startSmsRetriever(),会导致进入验证码页时可能已超时(默认超时5分钟),改为在进入验证码页时启动:
修改VerificationPhoneCodeRoute,添加LaunchedEffect启动SmsRetriever:
@Composable fun VerificationPhoneCodeRoute( navHostController: NavHostController, viewModel: VerificationPhoneCodeViewModel = hiltViewModel() ) { val state by viewModel.state.collectAsState() val context = LocalContext.current // 进入页面时启动SmsRetriever LaunchedEffect(Unit) { val client = SmsRetriever.getClient(context) val task = client.startSmsRetriever() task.addOnSuccessListener { // 启动成功 } task.addOnFailureListener { // 启动失败,提示用户手动输入 } } VerificationPhoneCodeScreen( navHostController = navHostController, state = state, onChangeOtpValue = viewModel::onChangeOtpValue, verificationSmsCode = viewModel::verificationSmsCode, setError = viewModel::setError, ) }
同时移除MainActivity中的startSmsRetriever()代码:
@AndroidEntryPoint class MainActivity : ComponentActivity() { lateinit var navHostController: NavHostController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 移除原有的SmsRetriever启动代码 setContent { DiyetkolikTheme { navHostController = rememberNavController() Surface(modifier = Modifier.fillMaxSize()) { RootNavGraph(navHostController = navHostController) } } } } }
注意事项
- 服务端发送的短信必须符合Google SMS Retriever格式:以
<#>开头,包含4位验证码,结尾以[你的应用包名]结束,例如:<#> 1234 是你的验证码。[com.example.diyetkolik] - 确保Manifest中广播接收器的权限和intent-filter配置正确,无需修改原有配置。
- 若使用Hilt注入Context,需在Application类添加
@HiltAndroidApp注解,并在Manifest中配置该Application。
内容的提问来源于stack exchange,提问作者NewPartizal
相关产品推荐
相关产品推荐

