nfc_util 2.2.0 copy "nfc_util: ^2.2.0" to clipboard
nfc_util: ^2.2.0 copied to clipboard

A Flutter plugin providing access to NFC features on Android and iOS.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:nfc_util/nfc_util.dart';
import 'package:nfc_util/platform_tags.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(MyApp());
}

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

  @override
  State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  ValueNotifier<dynamic> result = ValueNotifier(null);

  late Future<NfcAvailability> _availability;
  StreamSubscription<NfcAdapterState>? _adapterState;

  @override
  void initState() {
    super.initState();
    _availability = NfcManager.instance.checkAvailability();

    // Android only. The user can switch NFC on or off without leaving the app, so re-check
    // instead of leaving a stale "NFC is switched off" message on screen.
    _adapterState = NfcManager.instance.onAdapterStateChanged.listen((state) {
      debugPrint('NFCTEST adapter state -> ${state.name}');
      setState(() => _availability = NfcManager.instance.checkAvailability());
    });
  }

  @override
  void dispose() {
    unawaited(_adapterState?.cancel());
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Nfc Util Plugin Example')),
        body: SafeArea(
          child: FutureBuilder<NfcAvailability>(
            future: _availability,
            builder: (context, ss) => ss.data != NfcAvailability.enabled
                ? Center(
                    child: Text(switch (ss.data) {
                      NfcAvailability.disabled => 'NFC is switched off. Enable it in system settings.',
                      NfcAvailability.unsupported => 'This device does not support NFC.',
                      _ => 'Checking NFC availability...',
                    }, textAlign: TextAlign.center),
                  )
                : Flex(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    direction: Axis.vertical,
                    children: [
                      Flexible(
                        flex: 2,
                        child: Container(
                          margin: EdgeInsets.all(4),
                          constraints: BoxConstraints.expand(),
                          decoration: BoxDecoration(border: Border.all()),
                          child: SingleChildScrollView(
                            child: ValueListenableBuilder<dynamic>(
                              valueListenable: result,
                              builder: (context, value, _) => Text('${value ?? ''}'),
                            ),
                          ),
                        ),
                      ),
                      Flexible(
                        flex: 3,
                        child: GridView.count(
                          padding: EdgeInsets.all(4),
                          crossAxisCount: 2,
                          childAspectRatio: 4,
                          crossAxisSpacing: 4,
                          mainAxisSpacing: 4,
                          children: [
                            ElevatedButton(onPressed: _tagRead, child: Text('Tag Read')),
                            ElevatedButton(onPressed: _ndefWrite, child: Text('Ndef Write')),
                            ElevatedButton(onPressed: _ndefWriteLock, child: Text('Ndef Write Lock')),
                            ElevatedButton(onPressed: _tagIo, child: Text('Tag I/O')),
                            ElevatedButton(onPressed: _barcodeRead, child: Text('Barcode Read')),
                          ],
                        ),
                      ),
                    ],
                  ),
          ),
        ),
      ),
    );
  }

  /// The tag's UID as uppercase hex. Every technology map carries it under `identifier`;
  /// FeliCa reports it as `currentIDm` instead.
  static String _tagCode(NfcTag tag) {
    for (final tech in tag.data.values) {
      if (tech is! Map) continue;
      final id = tech['identifier'] ?? tech['currentIDm'];
      if (id is List<int> && id.isNotEmpty) {
        return id.map((e) => e.toRadixString(16).padLeft(2, '0').toUpperCase()).join(':');
      }
    }
    return '(no identifier)';
  }

  /// iOS reports a session that could not start (or was cancelled, or timed out) here
  /// rather than by failing `startSession`. Without this callback those failures are
  /// completely silent.
  Future<void> _onSessionError(NfcError error) async {
    // `code` is the precise CoreNFC failure; `type` collapses most of them into `unknown`,
    // so print both to see which one actually tells you anything.
    final line = 'session error: ${error.type.name} / ${error.code?.name ?? 'no code'}: ${error.message}';
    debugPrint('NFCTEST $line');
    result.value = line;
  }

  void _tagRead() {
    unawaited(
      NfcManager.instance.startSession(
        pollingOptions: {NfcPollingOption.iso14443},
        onError: _onSessionError,
        onDiscovered: (NfcTag tag) async {
          result.value = 'UID: ${_tagCode(tag)}\n\n${tag.data}';
          await NfcManager.instance.stopSession();
        },
      ),
    );
  }

  void _ndefWrite() {
    unawaited(
      NfcManager.instance.startSession(
        onError: _onSessionError,
        onDiscovered: (NfcTag tag) async {
          final ndef = Ndef.from(tag);
          if (ndef == null || !ndef.isWritable) {
            result.value = 'Tag is not ndef writable';
            await NfcManager.instance.stopSession(errorMessage: 'Tag is not ndef writable');
            return;
          }

          final message = NdefMessage([
            NdefRecord.createText('Hello World!'),
            NdefRecord.createUri(Uri.parse('https://flutter.dev')),
            NdefRecord.createMime('text/plain', Uint8List.fromList('Hello'.codeUnits)),
            NdefRecord.createExternal('com.example', 'mytype', Uint8List.fromList('mydata'.codeUnits)),
          ]);

          try {
            await ndef.write(message);
            result.value = 'Success to "Ndef Write"';
            await NfcManager.instance.stopSession();
          } on Object catch (e) {
            result.value = e;
            await NfcManager.instance.stopSession(errorMessage: e.toString());
          }
        },
      ),
    );
  }

  /// Runs read-only commands against whichever technologies the tag supports, so the
  /// platform's tag I/O path is exercised end to end. Nothing here writes to the tag.
  void _tagIo() {
    unawaited(
      NfcManager.instance.startSession(
        onError: _onSessionError,
        onDiscovered: (NfcTag tag) async {
          final started = DateTime.now();
          final log = StringBuffer('UID: ${_tagCode(tag)}\n\n');

          Future<void> step(String label, Future<Object?> Function() body) async {
            final at = DateTime.now().difference(started).inMilliseconds;
            String line;
            try {
              line = '${at}ms $label -> ${await body()}';
            } on Object catch (e) {
              final code = e is PlatformException ? '${e.code}: ${e.message}' : e.runtimeType.toString();
              line = '${at}ms $label !! $code';
            }
            log.writeln(line);
            debugPrint('NFCTEST $line');
            result.value = log.toString();
          }

          // Interleaved on purpose: if only the first call fails the problem is timing,
          // but if a given technology fails every time it is reached the problem is the
          // connect/close handling when switching technologies.
          final ndef = Ndef.from(tag);
          final nfcA = NfcA.from(tag);
          final ultralight = MifareUltralight.from(tag);
          final isoDep = IsoDep.from(tag);
          final miFare = MiFare.from(tag);

          if (ndef != null) {
            log.writeln('Ndef: isWritable=${ndef.isWritable} maxSize=${ndef.maxSize} canMakeReadOnly=${ndef.canMakeReadOnly}');
          }

          // Android only: give a slow tag more time than the platform default before the
          // exchange gives up and surfaces as `tag_lost`.
          if (isoDep != null) {
            await step('IsoDep.getTimeout()', isoDep.getTimeout);
            await step('IsoDep.setTimeout(2000)', () async => await isoDep.setTimeout(2000));
            await step('IsoDep.getTimeout()', isoDep.getTimeout);
            await step('IsoDep.getMaxTransceiveLength()', isoDep.getMaxTransceiveLength);
          }
          if (nfcA != null) {
            await step('NfcA.getTimeout()', nfcA.getTimeout);
            await step('NfcA.getMaxTransceiveLength()', nfcA.getMaxTransceiveLength);
          }

          for (var round = 1; round <= 2; round++) {
            if (ndef != null) {
              await step('[$round] Ndef.read()', () async => (await ndef.read())?.records.length);
            }
            if (nfcA != null) {
              // NTAG/Ultralight READ of page 0; harmless on other NfcA tags.
              await step('[$round] NfcA.transceive', () => nfcA.transceive(data: Uint8List.fromList([0x30, 0x00])));
            }
            if (ultralight != null) {
              await step('[$round] MifareUltralight.readPages', () => ultralight.readPages(pageOffset: 0));
            }
            if (isoDep != null) {
              // SELECT by name, empty AID: every ISO-DEP card answers something.
              await step('[$round] IsoDep.transceive', () => isoDep.transceive(data: Uint8List.fromList([0x00, 0xA4, 0x04, 0x00, 0x00])));
            }
            if (miFare != null) {
              await step('[$round] MiFare.sendMiFareCommand', () => miFare.sendMiFareCommand(Uint8List.fromList([0x30, 0x00])));
            }
          }

          if (ndef == null) log.writeln('Ndef.from(tag) -> null (tag is not NDEF formatted)');
          log.writeln('done, keep the tag still until this line appears');
          result.value = log.toString();

          await NfcManager.instance.stopSession();
        },
      ),
    );
  }

  /// Barcode tags are only discovered when `discoverNfcBarcode` is set, and skipping the
  /// NDEF probe is free here because a barcode tag has no NDEF content to read.
  void _barcodeRead() {
    unawaited(
      NfcManager.instance.startSession(
        discoverNfcBarcode: true,
        skipNdefCheck: true,
        onError: _onSessionError,
        onDiscovered: (NfcTag tag) async {
          final barcode = NfcBarcode.from(tag);
          result.value = barcode == null
              ? 'Not a barcode tag.\n\n${tag.data}'
              : 'type: ${barcode.barcodeType.name}\nbarcode: ${barcode.barcode}';
          await NfcManager.instance.stopSession();
        },
      ),
    );
  }

  void _ndefWriteLock() {
    unawaited(
      NfcManager.instance.startSession(
        onError: _onSessionError,
        onDiscovered: (NfcTag tag) async {
          final ndef = Ndef.from(tag);
          if (ndef == null) {
            result.value = 'Tag is not ndef';
            await NfcManager.instance.stopSession(errorMessage: 'Tag is not ndef');
            return;
          }

          try {
            await ndef.writeLock();
            result.value = 'Success to "Ndef Write Lock"';
            await NfcManager.instance.stopSession();
          } on Object catch (e) {
            result.value = e;
            await NfcManager.instance.stopSession(errorMessage: e.toString());
          }
        },
      ),
    );
  }
}
2
likes
160
points
388
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin providing access to NFC features on Android and iOS.

Repository (GitHub)
View/report issues

Topics

#nfc #ndef #mifare #felica

License

MIT (license)

Dependencies

flutter

More

Packages that depend on nfc_util

Packages that implement nfc_util