nfc_in_flutter 2.0.4

  • Readme
  • Changelog
  • Example
  • Installing
  • 94

nfc_in_flutter #

NFC in Flutter is a plugin for reading and writing NFC tags in Flutter. It works on both Android and iOS with a simple stream interface.

⚠️ Currently only NDEF formatted tags are supported.

Usage #

Read NFC tags #

// NFC.readNDEF returns a stream of NDEFMessage
Stream<NDEFMessage> stream = NFC.readNDEF();

stream.listen((NDEFMessage message) {
    print("records: ${message.records.length}");
});

Read one NFC tag #

NDEFMessage message = await NFC.readNDEF(once: true).first;
print("payload: ${message.payload}");
// once: true` only scans one tag!

Writing to tags #

You can access a message's NFC tag using the NDEFMessage's .tag property. The tag has a .write method, which allows you to write a NDEF message to the tag.

Note that the read stream must still be open when the .write method is called. This means that the once argument in .readNDEF() cannot be used.

Stream<NDEFMessage> stream = NFC.readNDEF();

stream.listen((NDEFMessage message) {
    NDEFMessage newMessage = NDEFMessage.withRecords(
        NDEFRecord.mime("text/plain", "hello world")
    );
    message.tag.write(newMessage);
});

You can also use the NFC.writeNDEF(NDEFMessage) method, which wraps the code above with support for the once argument.

NDEFMessage newMessage = NDEFMessage.withRecords(
    NDEFRecord.mime("text/plain", "hello world")
);
Stream<NDEFTag> stream = NFC.writeNDEF(newMessage);

stream.listen((NDEFTag tag) {
    print("wrote to tag");
});

If you only want to write to one tag, you can set the once argument to true.

NDEFMessage newMessage = NDEFMessage.withRecords(
    NDEFRecord.mime("text/plain", "hello world")
);
Stream<NDEFTag> stream = NFC.writeNDEF(newMessage, once: true);

stream.listen((NDEFTag tag) {
    print("only wrote to one tag!");
});

And if you would rather use a Future based API, you can await the returned stream's .first method.

NDEFMessage newMessage = NDEFMessage.withRecords(
    NDEFRecord.type("text/plain", "hello world")
);

await NFC.writeNDEF(newMessage, once: true).first;

Example #

import 'package:nfc_in_flutter/nfc_in_flutter.dart';

class NFCReader extends StatefulWidget {
    @override
    _NFCReaderState createState() => _NFCReaderState();
}

class _NFCReaderState extends State {
    bool _supportsNFC = false;
    bool _reading = false;
    StreamSubscription<NDEFMessage> _stream;

    @override
    void initState() {
        super.initState();
        // Check if the device supports NFC reading
        NFC.isNDEFSupported
            .then((bool isSupported) {
                setState(() {
                    _supportsNFC = isSupported;
                });
            });
    }

    @override
    Widget build(BuildContext context) {
        if (!_supportsNFC) {
            return RaisedButton(
                child: const Text("You device does not support NFC"),
                onPressed: null,
            );
        }

        return RaisedButton(
            child: Text(_reading ? "Stop reading" : "Start reading"),
            onPressed: () {
                if (_reading) {
                    _stream?.cancel();
                    setState(() {
                        _reading = false;
                    });
                } else {
                    setState(() {
                        _reading = true;
                        // Start reading using NFC.readNDEF()
                        _stream = NFC.readNDEF(
                            once: true,
                            throwOnUserCancel: false,
                        ).listen((NDEFMessage message) {
                            print("read NDEF message: ${message.payload}"),
                        }, onError: (e) {
                            // Check error handling guide below
                        });
                    });
                }
            }
        );
    }
}

Full example in example directory

Installation #

Add nfc_in_flutter to your pubspec.yaml

dependencies:
    nfc_in_flutter: 2.0.2

Version 2.0.3 broke more than it fixed, so please use version 2.0.2 until version 2.0.4 is released.

iOS #

On iOS you must add turn on the Near Field Communication capability, add a NFC usage description and a NFC entitlement.

Turn on Near Field Communication Tag Reading

Open your iOS project in Xcode, find your project's target and navigate to Capabilities. Scroll down to 'Near Field Communication Tag Reading' and turn it on.

Turning on 'Near Field Communication Tag reading'

  • Adds the NFC tag-reading feature to the App ID.
  • Adds the Near Field Communication Tag Reader Session Formats Entitlement to the entitlements file.

from developer.apple.com: Building an NFC Tag-Reader app

'Turn on Near Field Communication Tag Reading' capability  turned on for a project in Xcode

NFC Usage Description

Open your ios/Runner/Info.plist file and add a new NFCReaderUsageDescription key. It's value should be a description of what you plan on using NFC for.

<key>NFCReaderUsageDescription</key>
<string>...</string>

Android #

Add the following to your app's AndroidManifest.xml file:

<uses-permission android:name="android.permission.NFC" />

If your app requires NFC, you can add the following to only allow it to be downloaded on devices that supports NFC:

<uses-feature android:name="android.hardware.nfc" android:required="true" />

"What is NDEF?" #

If you're new to NFC you may come to expect a lot of readNFC() calls, but instead you see readNDEF() and NDEFMessage. NDEF is just a formatting standard the tags can be encoded in. There are other encodings than NDEF, but NDEF is the most common one. Currently NFC in Flutter only supports NDEF formatted tags.

Host Card Emulation #

NFC in Flutter supports reading from emulated host cards*.

To read from emulated host cards, you need to do a few things.

  • Call readNDEF() with the readerMode argument set to an instance of NFCDispatchReaderMode.
  • Insert the following <intent-filter /> in your AndroidManifest.xml activity:
<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>
  • Not properly tested on iOS

⚠️ Multiple reader modes #

If you start a readNDEF() stream with the reader mode set to an instance of NFCDispatchReaderMode, while another stream is active with the NFCNormalReaderMode, it will throw a NFCMultipleReaderModesException.

Platform differences #

When you call readNDEF() on iOS, Core NFC (the iOS framework that allows NFC reading) opens a little window. On Android it just starts listening for NFC tag reads in the background.

image from developer.apple.com: Near Field Communication

⚠️ This will also freeze Flutter while open. Please send a Pull Request if you can fix this.

Error handling #

Errors are no exception to NFC in Flutter (hah, get it). The stream returned by NFC.readNDEF() can send 7 different exceptions, and even worse: they are different for each platform!

See the full example in the example directory for an example on how to check for errors.

Exceptions for both platforms #

NDEFReadingUnsupportedException

Thrown when a reading session is started, but not actually supported.

iOS #

NFCUserCanceledSessionException

Thrown when the user clicks Cancel/Done core NFC popup. If you don't need to know if the user canceled the session you can start reading with the throwOnUserCancel argument set to false like so: readNDEF(throwOnUserCancel: false)

NFCSessionTimeoutException

Core NFC limits NFC reading sessions to 60 seconds. NFCSessionTimeoutException is thrown when the session has been active for 60 seconds.

NFCSessionTerminatedUnexpectedlyException

Thrown when the reading session terminates unexpectedly.

NFCSystemIsBusyException

Throw when the reading session fails because the system is too busy.

Android #

NFCIOException

Thrown when a I/O exception occurs. Will for example happen if a tag is lost while being read or a tag could not be connected to.

NDEFBadFormatException

Thrown when the tag is expected to NDEF formatted, but it is incorrectly formatted.

2.0.4 #

  • CoreNFC is now a weak_framework. This should fix a crash on iOS devices that doesn't support NFC. (credit to GitHub user @mxpazyj)
  • Added alertMessage argument to readNDEF(). This controls the message on the iOS NFC modal. (credit to GitHub user @dghilardi)
  • Actually fixed NDEFRecord.languageCode being ignored when writing
  • Support for reading and writing to empty tags on Android
  • Fixed CoreNFC crashing after cancelling reading multiple times in a row (credit to GitHub user @martyfuhry)

2.0.3 #

  • Fixed NDEFRecord.languageCode being ignored when writing

2.0.2 #

  • Fixed a crash when reading tags containing records with a custom url protocol and well known type URL

2.0.1 #

  • Fixed writing TNF text records on iOS (credit to GitHub user @janipiippow)

2.0.0 #

  • Added noSounds flag to NFCNormalReaderMode

On Android, this tells the system not to play sounds when a NFC chip is scanned.

  • Support for writing NDEF messages has been added

Get acccess to tags using the new .tag property on messages, which you can use to connect and write to tags.

  • Added the following methods for constructing NDEF messages:

NDEFRecord.empty for empty records

NDEFRecord.plain for text/plain records

NDEFRecord.type for records with custom types

NDEFRecord.text for records with well known text types

NDEFRecord.uri for records with well known URI types

NDEFRecord.absoluteUri

NDEFRecord.external

NDEFRecord.custom

  • COULD BE BREAKING: Records with type T and U (with well known TNF) will now be correctly constructed. URI records will have the URL prefix added to the .payload and Text records will now correctly have thr first prefix byte removed from the .payload. If you want the precise value, you can use the new .data property which excludes the URL prefix of URI records and language codes of Text records.

  • Added .data property to NDEFRecord which excludes URL prefixes and language codes from records with well known types.

  • Added .languageCode property to NDEFRecord which will contain the language code of a record with a well known text type.

  • Updated the .tnf property on NDEFRecords. This is now an enumerable (NFCTypeNameFormat) with it's value mapped to the correct TNF value. This works on both Android and iOS where as it previously did not.

1.2.0 #

  • Added id property to NDEFMessage which contains the NFC tag's UID
  • Support for more card types on Android

1.1.1 #

  • Bugfix: Android sessions are now closed properly

1.1.0 #

  • Added support for reading from emulated host cards

1.0.0 #

  • First release, woohoo!
  • Support for reading NDEF formatted NFC tags on both Android and iOS

example/lib/main.dart

import 'dart:async';

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

import './read_example_screen.dart';
import './write_example_screen.dart';

void main() => runApp(ExampleApp());

class ExampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("NFC in Flutter examples"),
        ),
        body: Builder(builder: (context) {
          return ListView(
            children: <Widget>[
              ListTile(
                title: const Text("Read NFC"),
                onTap: () {
                  Navigator.pushNamed(context, "/read_example");
                },
              ),
              ListTile(
                title: const Text("Write NFC"),
                onTap: () {
                  Navigator.pushNamed(context, "/write_example");
                },
              ),
            ],
          );
        }),
      ),
      routes: {
        "/read_example": (context) => ReadExampleScreen(),
        "/write_example": (context) => WriteExampleScreen(),
      },
    );
  }
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // _stream is a subscription to the stream returned by `NFC.read()`.
  // The subscription is stored in state so the stream can be canceled later
  StreamSubscription<NDEFMessage> _stream;

  // _tags is a list of scanned tags
  List<NDEFMessage> _tags = [];

  bool _supportsNFC = false;

  // _readNFC() calls `NFC.readNDEF()` and stores the subscription and scanned
  // tags in state
  void _readNFC(BuildContext context) {
    try {
      // ignore: cancel_subscriptions
      StreamSubscription<NDEFMessage> subscription = NFC.readNDEF().listen(
          (tag) {
        // On new tag, add it to state
        setState(() {
          _tags.insert(0, tag);
        });
      },
          // When the stream is done, remove the subscription from state
          onDone: () {
        setState(() {
          _stream = null;
        });
      },
          // Errors are unlikely to happen on Android unless the NFC tags are
          // poorly formatted or removed too soon, however on iOS at least one
          // error is likely to happen. NFCUserCanceledSessionException will
          // always happen unless you call readNDEF() with the `throwOnUserCancel`
          // argument set to false.
          // NFCSessionTimeoutException will be thrown if the session timer exceeds
          // 60 seconds (iOS only).
          // And then there are of course errors for unexpected stuff. Good fun!
          onError: (e) {
        setState(() {
          _stream = null;
        });

        if (!(e is NFCUserCanceledSessionException)) {
          showDialog(
            context: context,
            builder: (context) => AlertDialog(
              title: const Text("Error!"),
              content: Text(e.toString()),
            ),
          );
        }
      });

      setState(() {
        _stream = subscription;
      });
    } catch (err) {
      print("error: $err");
    }
  }

  // _stopReading() cancels the current reading stream
  void _stopReading() {
    _stream?.cancel();
    setState(() {
      _stream = null;
    });
  }

  @override
  void initState() {
    super.initState();
    NFC.isNDEFSupported.then((supported) {
      setState(() {
        _supportsNFC = true;
      });
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.grey,
      ),
      home: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.white,
          title: const Text('NFC in Flutter'),
          actions: <Widget>[
            Builder(
              builder: (context) {
                if (!_supportsNFC) {
                  return FlatButton(
                    child: Text("NFC unsupported"),
                    onPressed: null,
                  );
                }
                return FlatButton(
                  child:
                      Text(_stream == null ? "Start reading" : "Stop reading"),
                  onPressed: () {
                    if (_stream == null) {
                      _readNFC(context);
                    } else {
                      _stopReading();
                    }
                  },
                );
              },
            ),
            IconButton(
              icon: Icon(Icons.clear_all),
              onPressed: () {
                setState(() {
                  _tags.clear();
                });
              },
              tooltip: "Clear",
            ),
          ],
        ),
        // Render list of scanned tags
        body: ListView.builder(
          itemCount: _tags.length,
          itemBuilder: (context, index) {
            const TextStyle payloadTextStyle = const TextStyle(
              fontSize: 15,
              color: const Color(0xFF454545),
            );

            return Padding(
              padding: const EdgeInsets.fromLTRB(20, 10, 20, 10),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  const Text("NDEF Tag",
                      style: const TextStyle(fontWeight: FontWeight.bold)),
                  Builder(
                    builder: (context) {
                      // Build list of records
                      List<Widget> records = [];
                      for (int i = 0; i < _tags[index].records.length; i++) {
                        records.add(Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: <Widget>[
                            Text(
                              "Record ${i + 1} - ${_tags[index].records[i].type}",
                              style: const TextStyle(
                                fontSize: 13,
                                color: const Color(0xFF666666),
                              ),
                            ),
                            Text(
                              _tags[index].records[i].payload,
                              style: payloadTextStyle,
                            ),
                            Text(
                              _tags[index].records[i].data,
                              style: payloadTextStyle,
                            ),
                          ],
                        ));
                      }
                      return Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: records,
                      );
                    },
                  )
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  nfc_in_flutter: ^2.0.4

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:nfc_in_flutter/nfc_in_flutter.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
88
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
94
Learn more about scoring.

We analyzed this package on Apr 7, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.6
  • Flutter: 1.12.13+hotfix.8

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.8
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test