前編(NFCを活用したアプリ開発【前編】仕組みと活用例)ではNFCの仕組みやタグの種類、活用例を整理しました。後編は、実際にFlutterFlow + Custom Action(Dart)で「NFCの読み取り・書き込み」を行うミニアプリを作った記録と、手順・注意点のまとめです。結論から言うと、FlutterFlowでもNFCは動きます。ただし、いくつかの罠があります。FlutterFlowはノーコードツールの中でもかなり柔軟ですが、NFCのようなハードウェア寄りの機能は標準では扱えないものも多いです。だから今回は、FlutterFlowのUI設計をベースにしつつ、Dartで書いたCustom Actionを組み込む形でアプリを構築しました。両者のいいとこ取りをしながら、実際に動くところまで持っていくまでの流れを、順を追って紹介します。1. ゴールと前提ゴールタグのメタ情報取得NDEFの読み取りテキストの書き込み(Androidのみ)想定タグ: NTAG213/215/216(NDEF対応)対応状況Android: 読み取り/書き込みiOS: 読み取り/書き込み(バッググラウンドは不可)2. 実験環境項目内容使用ツールFlutterFlow(Blankプロジェクト)開発言語Dart(Custom Actionで記述)使用パッケージflutter_nfc_kit: ^3.6.0, ndef: ^0.3.4使用端末Pixel 9 (Android 14), iPhone 13 (iOS 18)NFCタグNTAG215(NDEF対応)3. アプリ構成(ミニマル)UIはとてもシンプルです。1ページにボタンを3つ置きました。メタ情報をスキャンNDEFを読み取るテキストを書き込む(入力欄付き)HomePage(UI) ├─ Button: メタ情報スキャン → Custom Action: scanNfcMeta ├─ TextField + Button: 書き込み → Custom Action: writeTextToNfc ├─ Button: NDEF読み取り → Custom Action: readNfcNdef └─ 下部に結果表示(nfcMetaJson / nfcNdefJson)状態は FFAppState に文字列で保持(nfcMetaJson, nfcNdefJson, nfcWrittenText)。4. 実装ステップ1) 依存パッケージを追加(FlutterFlow → Settings → Pubspec)NFC関連のFlutterパッケージはいくつかあります。nfc_manager, nfc_in_flutter, flutter_nfc_reader などがよく使われていますが、今回試した範囲では、flutter_nfc_kit: ^3.6.0 が一番バランスが良い印象でした。特に感じたのはこの3点です。iOS/Androidの両対応で、読み取り・書き込みがどちらも動いた他のライブラリだとiOS側のNDEF書き込みが未対応なものが多いですが、flutter_nfc_kit は比較的安定していました。APIの構造がシンプルで扱いやすいpoll() → readNDEFRecords() → writeNDEFRecords() の流れがわかりやすく、FlutterFlowのCustom Actionにも素直に組み込めました。Streamやイベントリスナー形式ではないので、非同期の扱いが少し楽です。メンテナンスが比較的活発リポジトリの更新が止まっていない点と、最近のAndroid/iOSバージョンでも動いた点が安心材料でした。もちろん、環境や端末によって結果が違う可能性もありますが、FlutterFlowのように「ノーコードに近い環境から直接呼び出す」前提だと、flutter_nfc_kit が一番扱いやすい印象でした。dependencies: flutter_nfc_kit: ^3.6.0 ndef: ^0.3.42) App State フィールドを作成書き込む内容や読み取った内容を保存するためのものです。Custom Codeからも簡単にアクセスできるので重宝します。nfcMetaJson(JSON)nfcWrittenText(String )nfcNdefJson(JSON)3) Custom Action を3つ作成3-1. タグのメタ情報取得 scanNfcMetaimport 'dart:convert';import 'package:flutter/services.dart';import 'package:flutter_nfc_kit/flutter_nfc_kit.dart';Future<void> scanNfcMeta() async { var appState = FFAppState(); try { // NFCチェック var availability = await FlutterNfcKit.nfcAvailability; if (availability != NFCAvailability.available) { throw Exception('NFCが利用できません'); } // タグをスキャン var tag = await FlutterNfcKit.poll(iosAlertMessage: 'タグをかざしてください'); // メタデータをJSON化 final tagJson = jsonEncode(tag.toJson()); // AppStateに保存 appState.update(() { appState.nfcMetaJson = tagJson; }); } finally { await FlutterNfcKit.finish(iosAlertMessage: 'スキャン完了'); }}3-2. テキスト書き込み writeTextToNfcimport 'package:flutter_nfc_kit/flutter_nfc_kit.dart';import 'package:ndef/ndef.dart' as ndef;Future<void> writeTextToNfc(String text) async { var appState = FFAppState(); try { print('NFCチェック開始'); var availability = await FlutterNfcKit.nfcAvailability; print('NFC利用可: $availability'); if (availability != NFCAvailability.available) { throw Exception('NFCが利用できません'); } print('タグスキャン開始...'); var tag = await FlutterNfcKit.poll( timeout: Duration(seconds: 10), iosAlertMessage: 'タグをかざしてください', ); print('タグ検出: ${jsonEncode(tag)}'); print('書き込み開始: $text'); var textRecord = ndef.TextRecord(text: text, language: 'en'); await FlutterNfcKit.writeNDEFRecords([textRecord]); print('書き込み成功!'); await FlutterNfcKit.finish(iosAlertMessage: '書き込み完了'); print('セッション終了'); } catch (e) { print('エラー発生: $e'); }}3-3. NDEF読み取り readNfcNdefimport 'dart:convert';import 'dart:typed_data';import 'package:flutter_nfc_kit/flutter_nfc_kit.dart';Future<void> readNfcNdef() async { var appState = FFAppState(); try { // NFC利用可チェック var availability = await FlutterNfcKit.nfcAvailability; if (availability != NFCAvailability.available) { throw Exception('NFCが利用できません'); } // タグスキャン開始 await FlutterNfcKit.poll(iosAlertMessage: 'タグをかざしてください'); // NDEFレコード読み取り var records = await FlutterNfcKit.readNDEFRecords(); // ▼ ヘルパー関数:Text Recordのデコード String decodeTextPayload(Uint8List payload) { if (payload.isEmpty) return ''; int statusByte = payload[0]; int langCodeLen = statusByte & 0x3F; // 下位6ビットが言語コード長 return utf8.decode(payload.sublist(1 + langCodeLen)); // 言語コードをスキップ } // ▼ JSON構築 var ndefList = records.map((r) { String decodedText = ''; if (r.payload != null && r.payload!.isNotEmpty) { decodedText = decodeTextPayload(r.payload!); } return { 'type': r.type, // レコードのタイプ 'decodedText': decodedText, // 実際のテキスト 'rawPayload': r.payload != null ? base64Encode(r.payload!) // 元データはbase64で保持 : '' }; }).toList(); String ndefJson = jsonEncode(ndefList); // AppStateに保存 appState.update(() { appState.nfcNdefJson = ndefJson; }); } finally { await FlutterNfcKit.finish(iosAlertMessage: '読み取り完了'); }}4) UI にアクションを配線「メタ情報スキャン」→ scanNfcMeta 成功時: FFAppState().nfcMetaJson = result.resultJson「NDEF読み取り」→ readNfcNdef 成功時: FFAppState().nfcNdefJson = result.resultJson「書き込み」→ writeTextToNfc(text: TextField.value) 成功時: FFAppState().nfcWrittenText = TextField.value5. プラットフォーム設定(ここ大事)Android(AndroidManifest.xml)<uses-permission android:name="android.permission.NFC" /><uses-feature android:name="android.hardware.nfc" android:required="false" />任意で、NDEFのMIMEを関連付けてアプリを起動したい場合:<intent-filter> <action android:name="android.nfc.action.NDEF_DISCOVERED" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="text/plain" /></intent-filter>iOS(Info.plist と Capabilities)<key>NFCReaderUsageDescription</key><string>NFCタグを読み取るために使用します。</string>さらに Xcode → Signing & Capabilities でNear Field Communication Tag Reading を追加(これを忘れると一生読めません)。Runner.entitlements に com.apple.developer.nfc.readersession.formats が入っていることを確認。6. 実機での検証結果操作Pixel 9 (Android 14)iPhone 13 (iOS 18)タグ検出(poll)✅✅メタ情報取得✅✅NDEF読み取り✅✅テキスト書き込み✅✅ロック済タグへの書き込み❌❌特にiPhoneは安定して書き込める印象。ただし「読み込み完了→タグを離す→すぐ再度書き込み」はセッションの扱いが微妙で、再試行が必要でした。7. 今後やりたいこと書き込み前に「このタグ書ける?」を自動チェックタグを読み込んだらアプリを自動起動(Deep Link連携)NFCタグ × IoT連携(例:照明・ToDo起動・出席チェックなど)8. まとめ結論として、FlutterFlowでも、Custom Actionを併用すればNFCは十分実用レベルで扱えます。最大の課題はプラットフォームの制約と端末差です。設計の段階で対応範囲をきちんと整理した上で、UI上のユーザーのサポートと実機での検証を繰り返していけば、安定した体験に近づけます。「かざす」という物理的な行為がアプリの起点になるのはとても面白く、どんなアプリが作れるかを考えるとワクワクします。日常の中に小さな自動化やチェックイン、IoTトリガーを自然に取り入れることができそうです。