Flutter NFC Development Guide

<\/script>\n
'; }, get iframeSnippet() { const domain = '{ SITE_DOMAIN }'; const type = '{ embed_type }'; const slug = '{ embed_slug }'; return ''; }, get activeSnippet() { return this.method === 'script' ? this.scriptSnippet : this.iframeSnippet; }, copySnippet() { navigator.clipboard.writeText(this.activeSnippet).then(() => { this.copied = true; setTimeout(() => { this.copied = false; }, 2000); }); } }" @keydown.escape.window="open = false" @click.outside="open = false">

Embed This Widget

Theme


      
    

Widget powered by . Free, no account required.

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.

| 4 min read

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.