nfc_in_flutter

NFC in Flutter 是一个用于在 Flutter 中读写 NFC 标签的插件。它通过简单的流接口同时支持 Android 和 iOS。

用法

读取 NFC 标签

// NFC.readNDEF returns a stream of NDEFMessage
Stream<NDEFMessage> stream = NFC.readNDEF();

stream.listen((NDEFMessage message) {
    print("records: ${message.records.length}");
});

读取一个 NFC 标签

NDEFMessage message = await NFC.readNDEF(once: true).first;
print("payload: ${message.payload}");
// once: true` only scans one tag!

写入标签

您可以通过 NDEFMessage.tag 属性访问消息的 NFC 标签。该标签有一个 .write 方法,允许您将 NDEF 消息写入标签。

请注意,调用 .write 方法时,读取流必须仍然打开。这意味着 .readNDEF() 中的 once 参数不能被使用。

Stream<NDEFMessage> stream = NFC.readNDEF();

stream.listen((NDEFMessage message) {
    NDEFMessage newMessage = NDEFMessage.withRecords(
        NDEFRecord.mime("text/plain", "hello world")
    );
    message.tag.write(newMessage);
});

您也可以使用 NFC.writeNDEF(NDEFMessage) 方法,它封装了上面的代码,并支持 once 参数。

NDEFMessage newMessage = NDEFMessage.withRecords(
    NDEFRecord.mime("text/plain", "hello world")
);
Stream<NDEFTag> stream = NFC.writeNDEF(newMessage);

stream.listen((NDEFTag tag) {
    print("wrote to tag");
});

如果您只想写入一个标签,可以将 once 参数设置为 true。

NDEFMessage newMessage = NDEFMessage.withRecords(
    NDEFRecord.mime("text/plain", "hello world")
);
Stream<NDEFTag> stream = NFC.writeNDEF(newMessage, once: true);

stream.listen((NDEFTag tag) {
    print("only wrote to one tag!");
});

如果您更喜欢使用基于 Future 的 API,可以等待返回流的 .first 方法。

NDEFMessage newMessage = NDEFMessage.withRecords(
    NDEFRecord.type("text/plain", "hello world")
);

await NFC.writeNDEF(newMessage, once: true).first;

示例

import 'package:nfc_in_flutter/nfc_in_flutter.dart';

class NFCReader extends StatefulWidget {
    @override
    _NFCReaderState createState() => _NFCReaderState();
}

class _NFCReaderState extends State {
    bool _supportsNFC = false;
    bool _reading = false;
    StreamSubscription<NDEFMessage> _stream;

    @override
    void initState() {
        super.initState();
        // Check if the device supports NFC reading
        NFC.isNDEFSupported
            .then((bool isSupported) {
                setState(() {
                    _supportsNFC = isSupported;
                });
            });
    }

    @override
    Widget build(BuildContext context) {
        if (!_supportsNFC) {
            return RaisedButton(
                child: const Text("You device does not support NFC"),
                onPressed: null,
            );
        }

        return RaisedButton(
            child: Text(_reading ? "Stop reading" : "Start reading"),
            onPressed: () {
                if (_reading) {
                    _stream?.cancel();
                    setState(() {
                        _reading = false;
                    });
                } else {
                    setState(() {
                        _reading = true;
                        // Start reading using NFC.readNDEF()
                        _stream = NFC.readNDEF(
                            once: true,
                            throwOnUserCancel: false,
                        ).listen((NDEFMessage message) {
                            print("read NDEF message: ${message.payload}"),
                        }, onError: (e) {
                            // Check error handling guide below
                        });
                    });
                }
            }
        );
    }
}

完整的示例请参见 example 目录

安装

nfc_in_flutter 添加到您的 pubspec.yaml 文件中

dependencies:
    nfc_in_flutter: ^2.0.3

iOS

在 iOS 上,您必须启用近场通信功能,添加 NFC 使用说明和 NFC 授权。

启用近场通信标签读取

在 Xcode 中打开您的 iOS 项目,找到项目的 Target,然后导航到 Capabilities。向下滚动到 'Near Field Communication Tag Reading' 并将其启用。

'启用近场通信标签读取'

  • 将 NFC 标签读取功能添加到 App ID。
  • 将 'Near Field Communication Tag Reader Session Formats' 授权添加到 entitlements 文件。

来自 developer.apple.com: Building an NFC Tag-Reader app

README-xcode

NFC 使用说明

打开您的 ios/Runner/Info.plist 文件,并添加一个新的 NFCReaderUsageDescription 键。它的值应该是一个描述您打算如何使用 NFC 的说明。

<key>NFCReaderUsageDescription</key>
<string>...</string>

Android

将以下内容添加到您的应用的 AndroidManifest.xml 文件中

<uses-permission android:name="android.permission.NFC" />

如果您的应用需要 NFC,您可以添加以下内容,使其只能在支持 NFC 的设备上下载

<uses-feature android:name="android.hardware.nfc" android:required="true" />

"什么是 NDEF?"

如果您是 NFC 新手,您可能会期望有很多 readNFC() 调用,但实际上看到的是 readNDEF()NDEFMessage。NDEF 只是标签可以编码的一种格式标准。除了 NDEF 之外还有其他编码方式,但 NDEF 是最常见的。目前,NFC in Flutter 只支持 NDEF 格式的标签。

主机卡模拟

NFC in Flutter 支持从模拟主机卡*读取。

要从模拟主机卡读取,您需要做几件事。

  • 调用 readNDEF(),并将 readerMode 参数设置为 NFCDispatchReaderMode 的实例。
  • 将以下 <intent-filter /> 插入您的 AndroidManifest.xml activity 中
<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
  • 在 iOS 上未经过充分测试

⚠️ 多个读取模式

如果您启动一个将读取模式设置为 NFCDispatchReaderMode 实例的 readNDEF() 流,而另一个流以 NFCNormalReaderMode 激活,它将抛出 NFCMultipleReaderModesException

平台差异

当您在 iOS 上调用 readNDEF() 时,Core NFC(允许 NFC 读取的 iOS 框架)会打开一个小窗口。在 Android 上,它只是在后台开始监听 NFC 标签读取。

example

图片来自 developer.apple.com: Near Field Communication

⚠️ 这也会导致 Flutter 在打开时冻结。如果您能解决这个问题,请发送一个 Pull Request。

错误处理

错误也并非 NFC in Flutter 的例外(哈哈,懂了吧)。NFC.readNDEF() 返回的流可以发送 7 种不同的异常,更糟糕的是:它们在不同平台上是不同的!

请参阅 example 目录 中的完整示例,了解如何检查错误。

两个平台的异常

NDEFReadingUnsupportedException

当启动了一个读取会话,但实际上不支持时抛出。

iOS

NFCUserCanceledSessionException

当用户点击核心 NFC 弹窗的“取消/完成”时抛出。如果您不需要知道用户是否取消了会话,可以将 throwOnUserCancel 参数设置为 false 来开始读取,例如:readNDEF(throwOnUserCancel: false)

NFCSessionTimeoutException

Core NFC 将 NFC 读取会话限制为 60 秒。当会话活动 60 秒后,将抛出 NFCSessionTimeoutException

NFCSessionTerminatedUnexpectedlyException

当读取会话意外终止时抛出。

NFCSystemIsBusyException

当读取会话由于系统过于繁忙而失败时抛出。

Android

NFCIOException

当发生 I/O 异常时抛出。例如,当标签在读取过程中丢失或无法连接到标签时会发生这种情况。

NDEFBadFormatException

当标签被期望为 NDEF 格式,但格式不正确时抛出。

GitHub

https://github.com/semlette/nfc_in_flutter