NFC Tags, NDEF and Android (with Kotlin)

In this article, you will learn how to add NFC tag reading to an Android app. It registers for auto-starting when the user taps a specific NDEF NFC tag with the phone. In addition, the app reads the NDEF records from the tag.

Transmission speeds are slow: the maximum is 424 kbit / sec. Sending an image over NFC would take minutes. So, NFC is only used for short transactions (authentication, room access, payments), and as a trigger for other actions (open web sites, pair with Bluetooth or Wi-Fi).

NFC Operation Modes

Fully NFC capable phones support 3 modes of operation:

Reader / Writer: the phone reads data from an NFC tag or writes data back to it. The Core NFC API of Apple only supports reading open NFC tags containing NDEF messages. Android phones usually allow reading and writing, giving full access to the tag.

What’s inside NDEF? A tag can contain one or more NDEF messages. Given how small the storage area is, I’ve never seen more than one message on a tag in practice.

An NDEF message is composed of 1+ NDEF records. These contain the payload. Depending on the record type, it can be standardized content – like URLs, text, MIME types or handover information. Additionally, it’s possible to define custom content formats.

Android NFC Demo – Sample App

The sample contains a simple on-screen logger. You can add auto-formatted message to the log by providing header and text, e.g.:

Java

1

logMessage("Welcome","App started")

Note: The Android emulator doesn’t support NFC. You need a real phone to test the app.

Permissions & Required Features for NFC on Android

To add NFC support to the app, open AndroidManifest.xml. Add the following two lines:

Permission: required to access NFC hardware of your phone:

Permission to use NFC on Android

1

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

Declare Hardware Feature: inform external entities (most notably, the Google Play Store) that our app depends on NFC. If you set required to true, we make NFC support of the phone mandatory. The store only shows the app to NFC capable phones.

Query for NFC Support

Even though we specify the requirements and dependencies in the manifest, your app can still land on a non-supported device:

Either, the user installed the app manually and not through the Google Play Store. In this case, the uses-feature definition isn’t enforced.

Even if the phone supports NFC, it might currently be disabled in the settings.

So, it’s a good idea to check for NFC support when the app starts. Add the following lines to onCreate() in MainActivity:

Query for NFC support on Android

Java

1

2

3

varnfcAdapter=NfcAdapter.getDefaultAdapter(this)

logMessage("NFC supported",(nfcAdapter!=null).toString())

logMessage("NFC enabled",(nfcAdapter?.isEnabled).toString())

If
nfcAdapter is null, the phone doesn’t have NFC support. Only if
nfcAdapter.isEnabled returns true, we’re ready to go!

Launch Apps with NFC

Ultimately, we want to achieve the following: when the user taps our NFC tag, our app should launch and show the contents of the tag.

For launching our app, we use a deep link. In the Manifest, we register the URL of our website (or a specific sub-page of it). This has an important advantage: if the app is not yet installed, you show generic information on a website. That way, you’re always in control of the user experience. Place a link to the app store on that web page to “upgrade” the user.

Additionally, this method is fully compatible with the iPhone, which doesn’t support launching apps through NFC tags.

By default, Android shows a user consent dialog before launching your app. To bypass this, place a special JSON file on your web server. Read more in the Android Documentation about App Links.

Register for NFC Intents

Next, we register our app with Android. We want the app to be launched whenever a specific NDEF record is found on a tag. Add this intent filter to the Activity in your manifest:

Intent Filter for launching an app through the NDEF_DISCOVERED intent

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

<intent-filter>

<action android:name="android.nfc.action.NDEF_DISCOVERED"/>

<!--Defaultcategory isrequired forthe intent filter towork-->

<category android:name="android.intent.category.DEFAULT"/>

<!--Browsable also enables opening thisapp throughalink onawebsite-->

<category android:name="android.intent.category.BROWSABLE"/>

<!--Intent filters without data will never be triggered on NFC devices.

Receiving NFC Intents

Now, we have the permission to use NFC. Additionally, we told Android when to launch our application. How do we find out more about the NFC tag contents?

For example: in a museum use case where each exhibit has its own tag, you’d extract the unique part of the URL. Using the item name or ID found in the URL, you show the relevant information in the app.

We’re interested in two properties of the intent:

Action: the relevant intent has the action NfcAdapter.ACTION_NDEF_DISCOVERED

Data: the raw contents of the NDEF message are contained in the parcelable array extra called NfcAdapter.EXTRA_NDEF_MESSAGES

Intents & Running Android Apps

How does Android deliver the intent to our app?

onCreate(): If our app is launched through the NFC tag, Android creates a new instance of our activity. We retrieve the NDEF data through the Activity’s intent property.

onNewIntent(): What happens if our activity is already open? By default, Android starts a new activity instance. To prevent this behavior, set the launchMode to singleTop in the manifest. Then, or existing activity gets the intent through onNewIntent() – it’s no longer re-started.

Handle Incoming NFC Intents

No matter which way the intent takes to reach our app, it’s a good idea to centralize handling it. Create a new function called processIntent() and call it from both entry points:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

override fun onCreate(savedInstanceState:Bundle?){

// ...

if(intent!=null){

processIntent(intent)

}

}

override fun onNewIntent(intent:Intent?){

super.onNewIntent(intent)

if(intent!=null){

processIntent(intent)

}

}

In onCreate(), we take the intentproperty of the activity (getIntent() in Java). In onNewIntent(), we use the intentparameter.

Retrieve NDEF Message from the Intent

In our custom processIntent() function, we finally investigate the NFC tag contents. The following snippet checks the intent type and extracts the raw NDEF message array:

Test the app on your phone and place a breakpoint after extracting the NDEF message. Tap your NFC tag.

You can see that the message contains a single record. Most important is its payload: a byte array.

In the image above, the first byte is 0x02. Writable space is scarce on NFC tags. The most common URI schemes are abbreviated into a single byte, based on a standardized list. 0x02 means “https://www.”. Instead of 12 bytes, you only need 1 byte. If your whole tag only has 48 bytes of storage memory, that’s a big difference. Check the complete list or URI schemes in the NDEF library.

The remaining bytes are direct ASCII codes of the URL written to the NFC tag. The URL inside the record is UTF-8 encoded.

Extract Data from the NDEF Message

All the data we need is visible in the screenshot of the inspector window above. Let’s write the code to extract the URL.

Unique Link to your App

To ensure no other app hijacks your URLs, Android offers another option: the Android Application Record (AAR). In addition to the URL record, you add a second record to the NDEF message. It contains the package name of your app.

Android will then always launch your app when it discovers a tag that includes the AAR. In case your app isn’t installed yet, it will open the Google Play Store.

You can easily include an AAR with the NXP TagWriter. Activate the “Add launch application” option when writing your tag:

Even with an additional AAR, your app still launches the same way. You get the NDEF message through the intent as described before. Therefore, it’s always a good idea to add an Android Application Record to your NFC tag if you want a unique association with your app.