Using NFC Tags with Flutter
Introduction
I recently developed functionality to allow users to sign into an app using a business card. The business card has an embedded batteryless, short-range communication device known as a Near Field Communication (NFC) tag. The user simply positions the business card close to their mobile device (usually top rear) and data is transferred from the static , wireless tag to the mobile device. Power is transferred from the mobile device to the NFC tag when placed in close proximity thus the tag containing the data does not require its own power supply. I have received a number of messages asking me to document the process.
Gamification Use Case
The app is a mobile adventure game in which the user plays the role of a detective. The app is accompanied by a boxed product so the NFC enabled detective’s ID card is sold as part of the boxed game. NFC tags are quite inexpensive and come in a range of formats including featherweight stickers, embedded cards, dongles and so forth. They hold potential for a range of user cases — wearable, toys, games as well as implementations requiring greater security such as contactless payments and clocking in systems.
I store a unique identifier text string to associate the card with an app user who is then authenticated anonymously with Google’s Firebase Authentication. Although, NFC tags may contain phone numbers, web links (url), geographical points of interest , contact details as well as specific device and app action triggers.
Flutter Plugin
Flutter, Google’s cross-platform UI toolkit for building natively compiled apps for mobile and other devices, is a wildly popular framework with an expansive ecosystem including pub.dev a searchable repository of packages for Flutter and Dart, the language. There are already quite a number of existing plugins for accessing NFC features. I chose to use nfc_manager for my project as it supports both Android and iOS and offers a simple interface for handling a range of NFC tags.
Platform Setup
Android requires a use permission in the app’s main manifest file, as follows:
Please see the plugin homepage for instructions for iOS.
NFC Data Exchange Format
I am working with Ndef (NFC Data Exchange Format), a standardised data format which works with key notions of Messages and Records. A Message is the transportation mechanism while the record contains a specific payload.
I am using Flutter Bloc to help separate presentation from business logic, so I am interacting with the NFC plugin within a bloc dedicated to this logic. The bloc converts incoming events (triggered within the presentation layer) into states which provide the current context to the presentation layer. These states are emitted by an async generator using the yield keyword in the form of a stream. This isn’t a requirement to use NFC or the plugin it’s just the way I like to work.
To start an NFC session I add a StartNfcEvent instance to the bloc from within my presentation layer.
The StartNfcEvent class is just a simple custom class. It may contain configuration details, for example, in respect to the purpose of the NFC user interaction. I my example I merely use the instance type as an indicator of the desired event. In this case to start NFC.
When the StartNfcEvent instance is detected the first task is to ensure that NFC is available.
If it is not available then I yield a relevant state to inform my presentation layer which in turn provides the user with an appropriate message. Otherwise, a session may be started
The unawaited method is a feature of Google Pedantic, a component for maintaining best practiced for writing Dart code. Depending on your case, you may need to await the startSession to complete.
The only required parameter of the startSession method is onDiscovered, which accepts an anonymous function as its argument. The function is provided with an instanceof NfcTag when an NFT interaction is detected.
As I am specifically targetting Ndef format tags, the NfcTag is used to hydrate an object from an Ndef class, provided with the package.
My use case is quite simple, I only want to retrieve a single text value. Therefore I extract the first (which should be the only) record. The record is encoded as ASCII bytes therefore I use dart’s AsciiCodec implementation to decode into a string.
The decodedPayload variable will now hold the raw string value.
Important
Ndef Text format records ( https://help.gototags.com/article/nfc-ndef-text-record/) are prefixed with extra bytes including a language code and encoding type. These can be accessed and used for internationalized apps where the text content has a language context. In my case, this is not required so I have used a string manipulation function to simply split the final string based on a given prefix.
So, for example, if the language is english then the text ‘hello world’ once ascii decoded would provide the following string: enhelloworld
This seems to be a fairly common cause of confusion. Especially in cases where the Text format is being used in the wrong context like this example.
Although, it’s a bit rough and tumble, my subString action normalises the final string for my purpose and I am then able to cross reference within my database to identify the user. Note, that in my user case, the NFC usage provides a gamified sign in experience to a game. For apps which manage sensitive data additional security steps should be taken. My example here is for a simple plain text transfer from a wireless, batteryless, contactless and inexpensive accessory that works perfectly for my own scenario.
Originally published at https://blog.matwright.dev on May 19, 2021.