Flutter短信自动检测插件sms_autodetect的使用
Flutter短信自动检测插件sms_autodetect的使用
一、简介
sms_autodetect
是一个Flutter插件,用于提供OTP(一次性密码)自动填充支持。对于iOS,系统默认提供了SMS自动检测功能,因此不需要额外的插件。但对于Android,这个插件非常有用,因为它可以通过Google的SMSRetriever API来实现自动检测和填充短信验证码,而无需请求读取短信的权限。
二、使用方法
1. 监听短信验证码
在发送手机号码给后端之前,你需要告诉插件开始监听包含验证码的短信。你可以通过调用 SmsAutoDetect().listenForCode
来实现这一点:
await SmsAutoDetect().listenForCode;
这将使插件在收到短信时自动填充 PinCodeTextField
小部件中的验证码。
2. 使用 PinCodeTextField
小部件
PinCodeTextField
是一个用于输入验证码的自定义小部件。你可以根据需要配置它的样式和行为。以下是一个完整的 PinCodeTextField
示例:
PinCodeTextField(
autoDisposeControllers: false,
appContext: context,
pastedTextStyle: TextStyle(
color: Colors.green.shade600,
fontWeight: FontWeight.bold,
),
length: 6, // 验证码长度为6位
obscureText: true, // 是否隐藏输入内容
obscuringCharacter: '*', // 隐藏字符
// 如果使用了 obscuringCharacter,则不要使用 obscuringWidget
// obscuringWidget: Icon(Icons.vpn_key_rounded),
blinkWhenObscuring: true, // 当隐藏字符时是否闪烁
animationType: AnimationType.fade, // 动画类型
validator: (v) {
if (v!.length < 6) {
return "请输入有效的验证码";
} else {
return null;
}
},
pinTheme: PinTheme(
fieldOuterPadding: EdgeInsets.only(left: 8, right: 8),
shape: PinCodeFieldShape.box, // 输入框形状
borderRadius: BorderRadius.circular(5), // 圆角
fieldHeight: 50, // 输入框高度
fieldWidth: 45, // 输入框宽度
activeFillColor: Colors.white, // 激活时的背景颜色
inactiveFillColor: Colors.white, // 未激活时的背景颜色
selectedColor: Colors.black54, // 选中时的边框颜色
selectedFillColor: Colors.white, // 选中时的背景颜色
inactiveColor: Colors.black54, // 未激活时的边框颜色
activeColor: Colors.black54, // 激活时的边框颜色
),
cursorColor: Colors.black, // 光标颜色
animationDuration: Duration(milliseconds: 300), // 动画持续时间
enableActiveFill: true, // 是否启用激活时的填充效果
autoDismissKeyboard: false, // 验证码输入完成后是否自动收起键盘
controller: textEditingController, // 文本控制器
keyboardType: TextInputType.number, // 键盘类型
mainAxisAlignment: MainAxisAlignment.center, // 输入框的对齐方式
boxShadows: [
BoxShadow(
offset: Offset(0, 1),
color: Colors.black12,
blurRadius: 5,
)
],
onCompleted: (v) {
print("已完成输入");
},
onTap: () {
print("点击输入框");
},
onChanged: (value) {
print("当前输入的值: $value");
},
beforeTextPaste: (text) {
print("允许粘贴的内容: $text");
// 如果返回 true,则会显示粘贴确认对话框;否则不会有任何操作
return true;
},
)
3. 自动收起键盘
如果你希望在验证码输入完成后自动收起键盘,可以将 autoDismissKeyboard
设置为 true
:
autoDismissKeyboard: true,
4. Android SMS 约束
为了确保验证码能够被正确接收,短信内容需要遵循以下规则:
- 短信长度不能超过140个字节。
- 短信中必须包含一个一次性验证码,该验证码将由客户端发送回服务器以完成验证流程。
- 短信末尾必须包含一个11个字符的哈希字符串,用于标识你的应用。
例如,短信内容可以是:
ExampleApp: Your code is 123456
AC+7eBC8WFi
5. 自定义 CodeAutoDetect
如果你想要创建一个自定义的小部件来自动检测短信验证码,可以使用 SMSAutoFill
混入(mixin)。它提供了以下方法:
listenForCode()
:在initState
中调用,用于监听来自原生插件的短信验证码。cancel()
:在dispose
中调用,用于取消对短信验证码的监听。codeUpdated()
:当接收到验证码时调用,你可以通过code
字段访问验证码。unregisterListener()
:在dispose
中调用,用于注销广播接收器。
6. 获取应用签名
为了确保短信中的哈希字符串与应用匹配,你需要获取应用的签名。可以通过调用 SmsAutoDetect().getAppSignature
来获取应用签名:
Future<String> getAppSignature() async {
final String? appSignature = await _channel.invokeMethod('getAppSignature');
return appSignature ?? '';
}
三、完整示例代码
以下是一个完整的示例代码,展示了如何使用 sms_autodetect
插件来实现短信验证码的自动检测和填充:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:sms_autodetect/sms_autodetect.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
scaffoldBackgroundColor: Colors.white,
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue).copyWith(
background: Colors.white,
),
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
[@override](/user/override)
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SMSAutoFill {
final textEditingController = TextEditingController();
String signature = "{{ app signature }}";
[@override](/user/override)
void initState() {
super.initState();
}
[@override](/user/override)
void dispose() {
SmsAutoDetect().unregisterListener(); // 注销广播接收器
super.dispose();
}
[@override](/user/override)
void codeUpdated(String code, String msg) {
textEditingController.text = code; // 将接收到的验证码设置到文本控制器中
setState(() {}); // 刷新UI
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
home: Scaffold(
appBar: AppBar(
title: const Text('Otp Auto Detect'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
PhoneFieldHint(
autoFocus: true,
decoration: InputDecoration(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(width: 2, color: Colors.grey),
borderRadius: BorderRadius.circular(5),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 2, color: Colors.grey),
borderRadius: BorderRadius.circular(5),
),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 2, color: Colors.grey),
borderRadius: BorderRadius.circular(5),
),
suffixIconConstraints: BoxConstraints(
minWidth: 45,
minHeight: 0,
),
),
),
Spacer(),
PinCodeTextField(
autoDisposeControllers: false,
appContext: context,
pastedTextStyle: TextStyle(
color: Colors.green.shade600,
fontWeight: FontWeight.bold,
),
length: 6,
obscureText: true,
obscuringCharacter: '*',
// 如果使用了 obscuringCharacter,则不要使用 obscuringWidget
// obscuringWidget: Icon(Icons.vpn_key_rounded),
blinkWhenObscuring: true,
animationType: AnimationType.fade,
validator: (v) {
if (v!.length < 6) {
return "请输入有效的验证码";
} else {
return null;
}
},
pinTheme: PinTheme(
fieldOuterPadding: EdgeInsets.only(left: 8, right: 8),
shape: PinCodeFieldShape.box,
borderRadius: BorderRadius.circular(5),
fieldHeight: 50,
fieldWidth: 45,
activeFillColor: Colors.white,
inactiveFillColor: Colors.white,
selectedColor: Colors.black54,
selectedFillColor: Colors.white,
inactiveColor: Colors.black54,
activeColor: Colors.black54,
),
cursorColor: Colors.black,
animationDuration: Duration(milliseconds: 300),
enableActiveFill: true,
autoDismissKeyboard: false,
controller: textEditingController,
keyboardType: TextInputType.number,
mainAxisAlignment: MainAxisAlignment.center,
boxShadows: [
BoxShadow(
offset: Offset(0, 1),
color: Colors.black12,
blurRadius: 5,
)
],
onCompleted: (v) {
print("已完成输入");
},
onTap: () {
print("点击输入框");
},
onChanged: (value) {
print("当前输入的值: $value");
},
beforeTextPaste: (text) {
print("允许粘贴的内容: $text");
// 如果返回 true,则会显示粘贴确认对话框;否则不会有任何操作
return true;
},
),
Spacer(),
ElevatedButton(
child: Text('监听短信验证码'),
onPressed: () async {
await SmsAutoDetect().listenForCode; // 开始监听短信验证码
},
),
ElevatedButton(
child: Text('设置验证码为 123456'),
onPressed: () async {
setState(() {
textEditingController.text = '123456'; // 手动设置验证码
});
},
),
SizedBox(height: 8.0),
Divider(height: 1.0),
SizedBox(height: 4.0),
Text("应用签名: $signature"),
SizedBox(height: 4.0),
ElevatedButton(
child: Text('获取应用签名'),
onPressed: () async {
signature = await SmsAutoDetect().getAppSignature; // 获取应用签名
setState(() {});
},
),
ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => OtpCodeVerificationScreen(),
),
);
},
child: Text("测试 SmsAutoFill 混入"),
)
],
),
),
),
);
}
}
class OtpCodeVerificationScreen extends StatefulWidget {
const OtpCodeVerificationScreen({Key? key}) : super(key: key);
[@override](/user/override)
_OtpCodeVerificationScreenState createState() => _OtpCodeVerificationScreenState();
}
class _OtpCodeVerificationScreenState extends State<OtpCodeVerificationScreen> with SMSAutoFill {
String? appSignature;
String? otpCode;
String? message;
TextEditingController textEditingController = TextEditingController();
StreamController<ErrorAnimationType>? errorController;
bool hasError = false;
String currentText = "";
final formKey = GlobalKey<FormState>();
[@override](/user/override)
void codeUpdated(String code, String msg) {
otpCode = code;
message = msg;
textEditingController.text = otpCode!;
print("接收到的验证码: $otpCode");
print("接收到的消息: $message");
setState(() {});
listenForCode();
}
[@override](/user/override)
void initState() {
super.initState();
listenForCode();
errorController = StreamController<ErrorAnimationType>();
SmsAutoDetect().getAppSignature.then((signature) {
setState(() {
appSignature = signature;
debugPrint("---------------------\n 应用签名 $appSignature \n---------------------");
});
});
}
[@override](/user/override)
void dispose() {
errorController!.close();
super.dispose();
cancel();
}
snackBar(String? message) {
return ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message!),
duration: Duration(seconds: 2),
),
);
}
[@override](/user/override)
Widget build(BuildContext context) {
final textStyle = TextStyle(fontSize: 18);
return Scaffold(
appBar: AppBar(
title: Text("Otp Auto Detect"),
),
body: Column(
children: [
SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
'手机号码验证',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 22),
textAlign: TextAlign.center,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 8),
child: RichText(
text: TextSpan(
text: "请输入发送到 ",
children: [
TextSpan(
text: "+123456789120",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
],
style: TextStyle(
color: Colors.black54,
fontSize: 15,
),
),
textAlign: TextAlign.center,
),
),
SizedBox(
height: 20,
),
Padding(
padding: const EdgeInsets.fromLTRB(32, 32, 32, 0),
child: Text(
"当前应用签名: $appSignature",
),
),
const Spacer(),
PinCodeTextField(
autoDisposeControllers: false,
appContext: context,
pastedTextStyle: TextStyle(
color: Colors.green.shade600,
fontWeight: FontWeight.bold,
),
length: 6,
obscureText: false,
obscuringCharacter: '*',
// 如果使用了 obscuringCharacter,则不要使用 obscuringWidget
// obscuringWidget: Icon(Icons.vpn_key_rounded),
blinkWhenObscuring: true,
animationType: AnimationType.fade,
validator: (v) {
return null;
},
pinTheme: PinTheme(
fieldOuterPadding: EdgeInsets.only(left: 8, right: 8),
shape: PinCodeFieldShape.box,
borderRadius: BorderRadius.circular(5),
fieldHeight: 50,
fieldWidth: 40,
activeFillColor: Colors.white,
inactiveFillColor: Colors.white,
selectedColor: Colors.black54,
selectedFillColor: Colors.white,
inactiveColor: Colors.black54,
activeColor: Colors.black54,
),
cursorColor: Colors.black,
animationDuration: Duration(milliseconds: 300),
enableActiveFill: true,
errorAnimationController: errorController,
controller: textEditingController,
keyboardType: TextInputType.number,
mainAxisAlignment: MainAxisAlignment.center,
autoDismissKeyboard: true,
boxShadows: [
BoxShadow(
offset: Offset(0, 1),
color: Colors.black12,
blurRadius: 10,
)
],
onCompleted: (v) {
print("已完成输入");
},
onTap: () {
print("点击输入框");
},
onChanged: (value) {
print("当前输入的值: $value");
setState(() {
currentText = value;
});
},
beforeTextPaste: (text) {
print("允许粘贴的内容: $text");
return true;
},
),
const Spacer(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Builder(
builder: (_) {
if (otpCode == null) {
return Text("正在监听验证码...", style: textStyle);
}
return Column(
children: [
Text("接收到的验证码: $otpCode", style: textStyle),
Text("接收到的消息: $message", style: textStyle),
],
);
},
),
),
const Spacer(),
],
),
);
}
}
更多关于Flutter短信自动检测插件sms_autodetect的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter短信自动检测插件sms_autodetect的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用sms_autodetect
插件来实现短信自动检测的示例代码。这个插件允许你的应用监听并处理收到的短信内容。
步骤 1: 添加依赖
首先,在你的pubspec.yaml
文件中添加sms_autodetect
依赖:
dependencies:
flutter:
sdk: flutter
sms_autodetect: ^x.y.z # 请将x.y.z替换为最新版本号
然后运行flutter pub get
来安装依赖。
步骤 2: 请求权限
在Android上,你需要请求RECEIVE_SMS
权限。编辑你的AndroidManifest.xml
文件,添加以下权限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yourapp">
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<application
... >
...
</application>
</manifest>
注意:从Android 10(API级别29)开始,普通应用无法再直接读取短信内容,除非设备被设置为“直接短信访问”模式。这通常意味着你的应用需要在特定的设备或特定的用户设置下运行。
步骤 3: 实现短信监听
在你的Flutter代码中,你可以使用SmsAutodetect
类来监听短信。下面是一个完整的示例:
import 'package:flutter/material.dart';
import 'package:sms_autodetect/sms_autodetect.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: SmsListenerPage(),
);
}
}
class SmsListenerPage extends StatefulWidget {
@override
_SmsListenerPageState createState() => _SmsListenerPageState();
}
class _SmsListenerPageState extends State<SmsListenerPage> {
final SmsAutodetect _smsAutodetect = SmsAutodetect();
@override
void initState() {
super.initState();
// 开始监听短信
_startSmsListening();
}
@override
void dispose() {
// 停止监听短信
_stopSmsListening();
super.dispose();
}
void _startSmsListening() {
_smsAutodetect.startSmsListener().then((subscription) {
subscription.onData((SmsMessage message) {
// 处理接收到的短信
print("Received SMS: ${message.body}");
// 你可以在这里更新UI或执行其他操作
});
subscription.onError((error) {
print("Error: $error");
});
subscription.onDone(() {
print("SMS listening stopped.");
});
}).catchError((error) {
print("Failed to start SMS listening: $error");
});
}
void _stopSmsListening() {
_smsAutodetect.stopSmsListener();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SMS Autodetect Example'),
),
body: Center(
child: Text('Waiting for SMS...'),
),
);
}
}
注意事项
- 权限处理:在真实的应用中,你需要处理权限请求的结果,确保用户已经授予了必要的权限。
- 设备兼容性:由于Android权限和隐私策略的变化,确保你的应用能够在目标设备上正确运行。
- UI更新:在接收到短信时,你可能需要在UI上进行更新。这通常涉及到使用
setState
方法来触发UI重建。
这个示例代码提供了一个基本的框架,你可以根据需要进行扩展和修改。