Flutter短信自动检测插件sms_autodetect的使用

发布于 1周前 作者 phonegap100 来自 Flutter

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

1 回复

更多关于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...'),
      ),
    );
  }
}

注意事项

  1. 权限处理:在真实的应用中,你需要处理权限请求的结果,确保用户已经授予了必要的权限。
  2. 设备兼容性:由于Android权限和隐私策略的变化,确保你的应用能够在目标设备上正确运行。
  3. UI更新:在接收到短信时,你可能需要在UI上进行更新。这通常涉及到使用setState方法来触发UI重建。

这个示例代码提供了一个基本的框架,你可以根据需要进行扩展和修改。

回到顶部