LoginSignup
1
0

Flutter で NFC タグの読み取り・書き込みを行う(iOS)

Posted at

Flutter での NFC の読み書きについての日本語の記事があまりなかったので、備忘録も兼ねてまとめてみました。

今回は諸事情により iOS アプリの開発を前提としています。

環境

  • M1 MacBook Air (2020)
    • macOS Sonoma 14.4
  • Flutter 3.19.2
  • Tools
    • Dart 3.3.0
    • DevTools 2.31.1
  • nfc_manager 3.4.0

前提条件

Flutter プロジェクトの作成が済んでいること

NFC Manager のインストール

NFC Manager を利用することで、 NFC の読み取り・書き込み機能を追加することができます。

flutter pub add nfc_manager

Entitlements の追加

アプリの Capability(ネイティブ機能)を管理する Entitlements に Near Field Communication Tag Reader Session Formats Entitlements を追加します。
Xcode で Runner > Signing & Capabilities > + Capability から Near Field Communication Tag Reading を追加すれば OK です。
また、直接 Runner.entitlements ファイルに以下を追記することでも同等の操作が可能です。

<key>com.apple.developer.nfc.readersession.formats</key>
	<array>
		<string>TAG</string>
	</array>

Info.plist の更新

ios/Info.plist に NFCReaderUsageDescription の項目を追加します。
string 部分は空文字列でなければ任意の文字列で良いです。

<key>NFCReaderUsageDescription</key>
<string>For NFC Reading / Writing</string>

また、 com.apple.developer.nfc.readersession.iso7816.select-identifiers と com.apple.developer.nfc.readersession.felica.systemcodes については、ドキュメントには必要に応じて追加すると書かれていますが、 FeliCa を利用しなくてもこれを記述しないと正しく動作しなかったため、 Info.plist に追記します。
iso7816.select-identifiers の値は読み取るカードの種類(運転免許証、マイナンバーカード、学生証など)によって異なりますが、今回は "00000000000000" と指定しました(おそらく、特定のカードに対応するコード以外を入力すれば任意のカードを読み取れるようになります)。
systemcode についても使用目的や事業者によって異なりますが、今回は NFC Forum が規定する NFC Data Exchange Format (NDEF) が格納されたシステムのコード "12FC" を記述します1

<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>00000000000000</string>
</array>
<key>com.apple.developer.nfc.readersession.felica.systemcodes</key>
<array>
    <string>12FC</string>
</array>

NFC Manager のセットアップ

まずは NFC の読み取り・書き込み処理を記述するファイルでプラグインをインポートします。

import 'package:nfc_manager/nfc_manager.dart';

実際に NFC 機能を利用する際、端末側で機能が利用可能かどうかをチェックします。

bool isNfcAvailable = await NfcManager.instance.isAvailable();

利用可能な場合は、 NFC のセッションを開始します。

NfcManager.instance.startSession(
    onDiscovered: (NfcTag tag) async {
        // 読み取り・書き込み処理
    },
);

処理が終わったら、セッションを終了します。

NfcManager.instance.stopSession();

NFC タグのインスタンスの作成

NFC タグを読み取り・書き込みする際、どの規格(データ形式)を利用するかをインスタンスの形式で指定できます。
例えば、標準の NDEF で処理を行う際は、次のように Ndef クラスのインスタンスを宣言し、エラーハンドリングを行います。

onDiscovered: (NfcTag tag) async {
    Ndef? ndef = Ndef.from(tag);
    if (ndef == null) {
        // エラー処理
        return;
    }
}

Ndef クラスの他にも、 FeliCa や MiFare クラスが用意されています(こちらを参照)。

NFC タグの読み取り

読み取りは Ndef クラスの read メソッドを利用します。

NdefMessage message = await ndef.read();

返り値は NdefMessage クラスで、 records プロパティからデータを取り出します。

List<NdefRecord> records = message.records;

records は NdefRecord のリストとなっているので、ループを回すなどして要素を取り出します。
書き込まれている内容は payload プロパティ( Uint8List )から取り出せます。

for (NdefRecord record in records) {
    Uint8List payload = record.payload;
    // payload を処理
}

実際に文字列としてデータを処理する場合は、 payload を UTF-8 などにデコードする必要があります。

String str = '';
for (NdefRecord record in records) {
    Uint8List payload = record.payload;
    str += utf8.decode(payload);
}

FeliCa クラスでは readWithoutEncryption メソッドを利用します(暗号化されていないデータの読み取り)。
返り値の型などはこちらを参照してください。

NFC タグへの書き込み

まず、利用する NFC タグに書き込みが可能かどうかをチェックします。

bool isWritable = ndef.isWritable;

書き込みには write メソッドを利用します。
引数は NdefMessage を指定する必要があるので、書き込むデータを NdefRecord に変換し、 NdefMessage のコンストラクタに渡します。

if (isWritable) {
    NdefRecord record = NdefRecord.createText("Hello World!");
    await ndef.write(NdefMessage([record]));
}

FeliCa では先ほどと同様に writeWithoutEncryption メソッドを利用します。

実機テスト

Android 端末では NFC 機能を擬似的に再現できるエミュレータが利用できますが、今回は iOS 端末をターゲットにしているので、実物の NFC タグを用意する必要があります。
ここではカードタイプの NTAG215 を使用します。品番によって書き込めるデータサイズなどが変わるので注意してください。

テストする iOS 端末を PC に接続して、ビルドを行います。今回はデバッグビルドでテストします。
ビルドに成功すると、 NFC タグの読み書きが行えます。今回は読み取り・書き込み・消去が行えるボタンを設置しました。

まずは空の(空でなくても構いませんが)NFC タグに書き込みを行います。
テキストボックスに書き込むテキストを入力して、 "Write NFC" ボタンをタップします。

すると NfcManager のセッションが開始するので、 NFC タグを端末の読み取り部分にかざします。

書き込みに成功すると、上部に "Write OK" のメッセージと書き込まれた内容が表示されます。
正しく書き込めているか確認するために、 "Read NFC" ボタンをタップし、同様に NFC タグをかざして読み取ります。

読み取りに成功すると、 "Read OK" のメッセージと読み取った内容が表示されます。正しく書き込めています。

実際に読み取りしてみると、読み取り速度はかなり高速に感じました。
NFC 便利ですね。

全体のコード

import 'dart:convert';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:nfc_manager/nfc_manager.dart';

void main() async {
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  String _message = '';

  void readNfc() async {
    final bool isNfcAvailable = await NfcManager.instance.isAvailable();
    if (!isNfcAvailable) {
      print('NFC is not available on this device');
    } else {
      print('NFC is available on this device');
      NfcManager.instance.startSession(
        onDiscovered: (NfcTag tag) async {
          Ndef? ndef = Ndef.from(tag);
          if (ndef == null) {
            print('Tag is not ndef');
            return;
          }
          NdefMessage message = await ndef.read();
          List<NdefRecord> records = message.records;
          String str = '';
          for (NdefRecord record in records) {
            Uint8List payload = record.payload;
            str += utf8.decode(payload);
          }
          setState(() {
            _message = 'Read OK: ${str.substring(3)}';
          });
          NfcManager.instance.stopSession();
        },
        onError: (dynamic error) {
          print(error.message);
          return Future.value();
        },
      );
    }
  }

  void writeNfc(String text) async {
    final bool isNfcAvailable = await NfcManager.instance.isAvailable();
    if (!isNfcAvailable) {
      print('NFC is not available on this device');
    } else {
      print('NFC is available on this device');
      NfcManager.instance.startSession(
        onDiscovered: (NfcTag tag) async {
          Ndef? ndef = Ndef.from(tag);
          if (ndef == null) {
            print('Tag is not ndef');
            return;
          }
          bool isWritable = ndef.isWritable;
          if (isWritable) {
            NdefRecord record = NdefRecord.createText(text);
            await ndef.write(NdefMessage([record]));
            setState(() {
              _message = (text.isEmpty) ? 'Clear OK' : 'Write OK: $text';
            });
          } else {
            print('Tag is read-only');
          }
          NfcManager.instance.stopSession();
        },
        onError: (dynamic error) {
          print(error.message);
          return Future.value();
        },
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    final writeController = TextEditingController();
    return MaterialApp(
      home: GestureDetector(
        onTap: () => primaryFocus?.unfocus(),
        child: Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Text(_message),
                TextButton(onPressed: readNfc, child: const Text('Read NFC')),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    SizedBox(
                      width: 200,
                      child: TextField(
                        controller: writeController,
                      ),
                    ),
                    TextButton(
                        onPressed: () => writeNfc(writeController.text),
                        child: const Text('Write NFC')),
                  ],
                ),
                TextButton(
                    onPressed: () => writeNfc(''),
                    child: const Text('Clear NFC')),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

参考

nfc_manager package documentation

Reading and writing NFC using NfcManager in Flutter

  1. FeliCa技術方式の各種コードについて

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0