Flutter NFC Development Guide
Cross-Platform NFC with nfc_manager
Building cross-platform NFC apps with Flutter using the nfc_manager package. Covers Android and iOS setup, NDEF operations, and HCE.
Flutter NFC Development Guide: Cross-Platform with nfc_manager
Flutter's cross-platform story is compelling for NFC: write once, ship on Android and iOS from a single Dart codebase. The nfc_manager package by Naoki Okada is the de-facto standard, wrapping Android's NfcAdapter and iOS's Core NFC behind a unified API while still exposing platform-specific tech objects when you need raw access.
Package Setup
Add to pubspec.yaml:
dependencies:
nfc_manager: ^3.3.0
Run flutter pub get. No additional Kotlin/Swift bridge code is required for basic NDEF operations.
Android: Add to AndroidManifest.xml inside <manifest>:
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
iOS: In ios/Runner/Info.plist, add:
<key>NFCReaderUsageDescription</key>
<string>Used to read and write NFC tags.</string>
Add the com.apple.developer.nfc.readersession.formats entitlement with value NDEF in ios/Runner/Runner.entitlements.
Availability Check
Always guard NFC calls with an availability check before presenting UI:
import 'package:nfc_manager/nfc_manager.dart';
Future<bool> isNfcAvailable() async {
return await NfcManager.instance.isAvailable();
}
On iOS, isAvailable() returns false on simulators and iPads without NFC hardware. On Android it reflects whether NFC is enabled in Settings.
Reading an NDEF Tag
The session-based API mirrors iOS Core NFC's sheet modal on iOS and fires silently on Android:
Future<void> readTag() async {
NfcManager.instance.startSession(
onDiscovered: (NfcTag tag) async {
final ndef = Ndef.from(tag);
if (ndef == null) {
// Tag is not NDEF-compatible
NfcManager.instance.stopSession(errorMessage: 'Tag is not NDEF.');
return;
}
final message = ndef.cachedMessage;
if (message == null) {
NfcManager.instance.stopSession(errorMessage: 'No NDEF message.');
return;
}
for (final record in message.records) {
_handleRecord(record);
}
NfcManager.instance.stopSession();
},
);
}
ndef.cachedMessage contains the message read during tag discovery — no extra round-trip required. Always call stopSession() in all code paths, including error branches; an open iOS session blocks the NFC sheet indefinitely.
Parsing NDEF Records
nfc_manager gives you raw NdefRecord objects. Parse ndef-uri and ndef-text records manually:
void _handleRecord(NdefRecord record) {
// URI record: TNF = 0x01, type = [0x55]
if (record.typeNameFormat == NdefTypeNameFormat.nfcWellknown &&
record.type.isNotEmpty &&
record.type[0] == 0x55) {
final prefix = _uriPrefixes[record.payload[0]] ?? '';
final uri = prefix + utf8.decode(record.payload.sublist(1));
debugPrint('URI: $uri');
return;
}
// Text record: TNF = 0x01, type = [0x54]
if (record.typeNameFormat == NdefTypeNameFormat.nfcWellknown &&
record.type.isNotEmpty &&
record.type[0] == 0x54) {
final langLen = record.payload[0] & 0x3F;
final text = utf8.decode(record.payload.sublist(1 + langLen));
debugPrint('Text: $text');
}
}
const _uriPrefixes = {
0x00: '', 0x01: 'http://www.', 0x02: 'https://www.',
0x03: 'http://', 0x04: 'https://', 0x05: 'tel:', 0x06: 'mailto:',
};
Use the NDEF Decoder to verify your parsing logic against known byte sequences.
Writing an NDEF Message
Future<void> writeUrl(String url) async {
NfcManager.instance.startSession(
onDiscovered: (NfcTag tag) async {
final ndef = Ndef.from(tag);
if (ndef == null || !ndef.isWritable) {
NfcManager.instance.stopSession(errorMessage: 'Tag not writable.');
return;
}
final record = NdefRecord.createUri(Uri.parse(url));
final message = NdefMessage([record]);
try {
await ndef.write(message);
NfcManager.instance.stopSession(alertMessage: 'Written!');
} catch (e) {
NfcManager.instance.stopSession(errorMessage: e.toString());
}
},
);
}
Check user-memory limits before writing large payloads. The Memory Calculator gives available bytes per chip family.
Platform-Specific Raw Access
For MIFARE operations or authentication beyond NDEF, access the tech objects directly:
// Android: MIFARE Ultralight raw page read
final mifareUltralight = MifareUltralight.from(tag);
if (mifareUltralight != null) {
final pages = await mifareUltralight.readPages(pageOffset: 4);
}
// Android: IsoDep for ISO 14443-4 APDU commands
final isoDep = IsoDep.from(tag);
if (isoDep != null) {
final response = await isoDep.transceive(data: Uint8List.fromList([0x00, 0xA4, 0x04, 0x00]));
}
iOS raw access is more restricted — Core NFC exposes NFCFeliCaTag, NFCMiFareTag, and NFCISO7816Tag through the nfc_manager iOS tech objects.
State Management Pattern
Wrap NFC state in a ChangeNotifier or Riverpod provider so the UI reacts to scan events without coupling to platform callbacks:
class NfcScanNotifier extends ChangeNotifier {
String? _lastUri;
bool _isScanning = false;
String? get lastUri => _lastUri;
bool get isScanning => _isScanning;
Future<void> startScan() async {
_isScanning = true;
notifyListeners();
NfcManager.instance.startSession(onDiscovered: (tag) async {
final ndef = Ndef.from(tag);
// ... parse ...
_isScanning = false;
notifyListeners();
NfcManager.instance.stopSession();
});
}
}
Error Handling Reference
| Exception | Cause | Fix |
|---|---|---|
NfcManagerException(unavailable) |
NFC disabled or absent | Show settings deeplink |
NfcManagerException(sessionTimeout) |
iOS 60s timeout | Restart session, show hint |
IOException: Tag was lost |
Tag moved too fast | Prompt user to hold steady |
FormatException |
Corrupt NDEF on tag | Re-format tag |
TagLostException |
Android mid-write tag loss | Retry with fresh tag |
See also: iOS Core NFC Programming | Android NFC Programming | Web NFC API Guide | NDEF Specification Deep Dive
자주 묻는 질문
Our guides cover a range of experience levels. Getting Started guides are written for beginners with no prior NFC knowledge. Programming guides target developers integrating NFC into mobile apps or embedded systems. Security guides are for engineers designing secure NFC deployments for payments, access control, or authentication.
Most guides require only an NFC-enabled smartphone (iPhone 7+ or any modern Android device) and a few NFC tags (NTAG213 or NTAG215 recommended for beginners, available for under $1 each). Advanced guides may reference USB NFC readers like the ACR122U or Proxmark3 for development and testing.
Yes. Programming guides include code examples for Android (Kotlin/Java with the Android NFC API), iOS (Swift with Core NFC), and web-based tools (Web NFC API for Chrome on Android). All code samples are tested and include inline comments explaining each step.