Flutter and NFC unleashed

Arnaudelub
7 min readMar 31, 2021

--

Playing with NFC is not an easy task and the documentation is hard to find on that subject. So when i was given this project from a Spanish startup company , i went through a lot a trouble. But from this trouble, i have also learn a lot and i want to share that knowledge with you and maybe save you some time.

1. The plugins

There are 2 plugins for handling NFC on pub.dev: FlutterNfcKit which works well with iOS and NfcInFlutter which works better with Android. You can try to use one of them for both platform but you’ll see that there is only a good support for one platform, at least at DateTime.now(). And NDEF to encode and decode the ndef records as flutter_nfc_kit does not do implement it.

2. NFC Crash course

I won’t explain how it works with symbols and stuff, i am just going to expose the basics, in case you want to know, if you don’t you can jump to part 3.

So there are 2 types of NFC devices, the Passive NFC devices, like your credit card for example, and the Active devices like…. yes indeed, your phone (if not too old:). Passive devices can’t connect to other passive devices, they can just store some information and send them to other Active devices, while Active devices can connect with other active devices and passive devices, read and write data. I think that’s enough, you got the concept!

3. Let’s get the party started

Okay, now that you know that you will have to handle 2 different plugins you will probably want to create a repository, and if you don’t, well, you should. I am not a big fan Apple, but in this case, the NFC chips works way better than the Android’s one, i have to admit, so let’s start with the easiest part… iOS with flutter_nfc_kit

On iOS

First of all, add the NFCReaderUsageDescription in your info.plist:

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

Then, in Xcode, select the TARGETS Runner , select the tab Signin and Capability and add the Near Field Communication Tag Reader.

And that’s it for the initial configuration.

So let’s start some coding, before anything, you want to check that the mobile phone has an NFC chip, and for that you can use:

await FlutterNfcKit.nfcAvailability;

We can start to build our repository with this:

Now that we know how to handle the devices without NFC, let’s keep going with the initialization of the NFC chip. On iOS, when the NFC chip is listening you are presented with a bottom modal that you can customize a little, only the text under between the image and the cancel button.

Customizing the iOS NFC scan alert

FlutterNfcKit has a method called poll() to start listening and will return and NFCTag object after successfully reading a tag, and this is where you can customize the alert, control the NFC technology you want to enable (readIso14443A, readIso14443B, readIso18092 and readIso15693):

When the chip is receiving the tag, while you are parsing the data, writing a new tag or anything else, you can modify the content of iosAlertMessage by using:

await ios.FlutterNfcKit.setIosAlertMessage("One moment please, i am doing some stuff");

Y cuando hayas terminado, puedes cerrar todo con un mensaje también:

await ios.FlutterNfcKit.finish(iosAlertMessage: "Hecho, bye!");

Now that you know how to control the alert, let’s see how to do the stuff in between, meaning writing, cleaning and reading the tag.

Reading the tag

So, we have received the tag from the poll method, if you want you can check if the type of the tag is the one you want with the enum NFCTagType, for instance:

if (tag.type == ios.NFCTagType.mifare_ultralight){
// do your stuff here
}

Well, each tag has a List of records in it, so to access them you can just do something like:

final ndefRecords = await ios.FlutterNfcKit.readNDEFRecords();
//We'll need to decode here the records with de ndef package
for(ndef.NDEFRecord record in ndefRecords){
//Now you have access to
// record.text
// record.language
// record.payload
// and more
}

Ok in the case of the app i was developing, i just needed to get a Uuid() from as text, so i wanted to use record.text to get the Uuid stored in the Reliqium necklace, but you can also get raw data with record.payload.

So to sum it up in our repo:

By default, the necklace, needed for the app i was building, is empty, so the tag being written is written by the application, so i am sure that there is only one TextRecord. So, now, here come our next step, writing a tag :)

Writing the tag

Ok so your passive NFC device is empty, and you want to write some id, text or other type of data in it. For this you will have to use the ndef plugin that offers different types like TextRecord, MimeRecord, TypeRecord and so on, check your IDE to see all the types:

ndef record types

Here is how to write a tag with FlutterNfcKit and the writeNDEFRecords() which accepts a list of records:

You can add the language in the TextRecord to suit your needs if you want to. We’ll see why we are adding this MimeRecord later when covering the android part.

So finally, here is how our repo looks like now:

Error handling

Yep, NFC can throw many kinds of error, so you should catch each of them and acting in consequences. If you have an error while writing or reading, you will receive a PlateformException, so you just need to catch it and check for the error message, you can do something like this:

const writeErrorMessage = "Write NDEF error";
const readErrorMessage = "Read NDEF error";
try{
//All of our previous code is here
[...]
} on PlatformException catch(error) {
if(error.message.contains(writeErrorMessage)) {
//Handle here reading error
} else if (error.message.contains(readErrorMessage)){
//Handle here writing error
} else {
// Handle here the other type of errors
}
}

And that’s it for the iOS, we got pretty much everything covered.

On Android

First of all, make sure to add the intent filter in your AndroidManifest.xml as mentioned in the package documentation:

<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

And that’s it, now let’s follow the same path, as iOS, first of all, check the availability with the plugin NfcInFlutter:

ok, nothing to comment in here, just NFC.isNDEFSupported

Now, to read the tag, the method is NDEF.readNDEF() and it return Stream<NDEFMessage>.

So to handle this stream, you will need a StreamSubscription to subscribe to the events or you can set the flag “once:true” and use .first on the returned Stream. Here is an example on how to implement the two solutions:

Ok so now you can see how to read the NDEFMessage, now, remember what i said about the content-type record, the reason is that on Android, when NFC is on, there is a default application on Android always listening, so when you scan a TAG with your phone, without any application open, you will see a window like this one:

Boring behavior

So when your app is opened and when you scan a TAG that contains a content-type with the package name in it, it will override the default behavior, but for that you have to add some code in your AndroidManifest.xml in the intent filter we add earlier:

<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<!-- add this line-->
<data android:mimeType="application/vnd.com.domain.appname"/>
</intent-filter>

Nice, problem solved!

Ok now, if you want to write a NDEFMessage, you can use the same principle as for reading a NDEFMessage with the flag “once”

Sum it up

ok, let’s implement all the logic to our repository and refactor the code:

As you can see, for the Android part, i am using the once flag, honestly, that’s not ideal because once scanned, the default Android NFC app will start listening and detecting the NFC device and you will hear the system notification. If you have done it correctly, the default NFC window won’t open, but still, i think that the best approach is to use the StreamSubscription() and close it with a Future.delayed() once the tag received, but it’s up to you. For instance:

Future<void> close() async {
Future.delayed(const Duration(seconds:5), () => ndefStreamSubscription?.cancel()));
}

Well, i think that all for now, if you want some help on how to use this repository with Bloc, please let me know and i’ll write a second part with it.

Happy coding to everyone!

--

--

Arnaudelub
Arnaudelub

Written by Arnaudelub

Flutter Developer enthousiast since 2018

Responses (1)