Android phone hovering over NFC tag

How to write an NFC tag from an Android app

Introduction

Writing data to NFC tags is often a misunderstood process, and so it can appear more difficult than it actually is. Android, Windows 8, and Windows Phone 8 have made the process easier than ever, having abstracted many of the nitty-gritty details into a few succinct APIs. The basic process is very similar, which is to detect a tag, connect to it, write a message, and close the connection.

In this article, we’ll go through a complete example of these 4 steps on Android, to write a simple text message to any NFC tag. As a result you will also be able to read from these tags by following along.

Detect a tag

Gone are the days where writing to an NFC tag was different depending on what tag technology was being used. MIFARE Ultralight, DESFire, Felica, and Topaz are all different tag types that behave differently at the low level. This used to mean that different code had to be written to handle each tag type, but not anymore.

If you are writing NFC data (better known as NDEF messages), in general, you don’t care what kind of tag you’re working with as they will all behave the same from your perspective. The OS handles that for us. We start with being notified the user just placed a tag on their device.

Add these lines to each document to get started:


AndroidManifest.xml
<uses-feature android:name="android.hardware.nfc" />
<uses-permission android:name="android.permission.NFC" />
<application
  ..
  <activity
    ..
    <intent-filter>
      <action android:name="android.nfc.action.NDEF_DISCOVERED" />
      <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
  </activity>
</application>

MainActivity.java
@Override
protected void onNewIntent(Intent intent){
  // If the intent caught is a NFC tag, handle it
  if(NfcAdapter.ACTION_TAG_DISCOVERED.equals(intent.getAction())){
    Tag myTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
    if (mytag != null) {
      // If you want to, here is where you can read information off of the tag
      // ie, myTag.getId() or intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
      WriteTextTag(“hello world”, myTag);
    }
  }
}

The additions to the manifest will allow your application to use the NFC hardware and open up when a supported tag is detected. The override in your activity will catch any intents and filter for NFC tags only, which is the same location your activity would run from other types of incoming intents.

Connect, write, and close

Now that we have the Tag, we want to get an NDEF object from it, as this is the level we work with NFC at. We also need to create an NDEF message with a text NDEF record in it. The way that NFC data is layered is that a tag typically contains zero or one NDEF messages, and the NDEF message can contain one or more NDEF records.

In this simple example, our NDEF message will contain a single record containing only text. More advanced uses will often have 3 different records, with each one serving a specific purpose or platform. After we prepare our data, the connect, write, and close process should be handled in a separate thread so as to keep the UI responsive.


MainActivity.java
private void WriteTextTag(String text, Tag tag) {
  try {
    // Create a text record according to the NFC Forum Text Record Type Definition
    NdefRecord[] records = { createTextRecord(text) };
    // Add that record to a message
    NdefMessage message = new NdefMessage(records);
    // Get an instance of Ndef for the tag
    Ndef ndef = Ndef.get(tag);
    // Do the connect and write in different thread
    new AsyncConnectWrite().execute(message, ndef);
  } catch (UnsupportedEncodingException e) {
    // US-ASCII character set should always be supported, but if not this will run
    e.printStackTrace();
  }
}
private NdefRecord createTextRecord(String text) throws UnsupportedEncodingException {
  // Convert the text to hex (with the default UTF-8) and get the length
  byte[] textBytes  = text.getBytes();
  int textLength = textBytes.length;
  // Convert to the text language to hex (ensuring US-ASCII) and get the length
  String lang       = “en”;
  byte[] langBytes  = lang.getBytes(“US-ASCII”);
  int langLength = langBytes.length;
  // Make an array of the proper size to hold the status byte, language, and text
  byte[] payload    = new byte[1 + langLength + textLength];
  // Set the status byte (which is only the length in our case of using UTF-8)
  payload[0] = (byte) langLength;
  // Copy langbytes and textbytes into the payload
  System.arraycopy(langBytes, 0, payload, 1, langLength);
 System.arraycopy(textBytes, 0, payload, 1 + langLength, textLength);
 // Create an Android NdefRecord with TNF=well known and RTD=text with the payload we created
// There is a single record, so no ID is needed
 return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], payload);
}
private class AsyncConnectWrite extends AsyncTask<Object, Void, String> {
        @Override
        protected String doInBackground(Object… params) {
            String result = “”;
            final String className = MainActivity.class.getSimpleName();

            NdefMessage message = (NdefMessage) params[0];
            Ndef ndef = (Ndef) params[1];
            try {
                if (ndef != null) {
                    // Try to connect to the tag
                    ndef.connect();
                    if (ndef.isConnected()) {
                        // Write the message
                        ndef.writeNdefMessage(message);
                    }
                }
            } catch (FormatException e) {
                Log.e(className, “FormatException while writing…”, e);
                result = “FormatException while writing”;
            } catch (TagLostException e) {
                Log.e(className, “TagLostException while writing…”, e);
                result = “TagLostException while writing”;
            } catch (IOException e) {
                Log.e(className, “IOException while writing…”, e);
                result = “IOException while writing”;
            }finally {
                try {
                    if (ndef != null) {
                        ndef.close();
                        result = “Message written!”;
                    }
                } catch (IOException e) {
                    Log.e(className, “IOException while closing…”, e);
                    result = “IOException while closing”;
                }
            }
            return result;
        }
        @Override
        protected void onPostExecute(String result) {
            if (!result.isEmpty()) {
                Toast.makeText(ctx, result, Toast.LENGTH_LONG).show();

            }
        }
    }

If you take the error handling out, it is rather straightforward after creating the text record. If you wanted to create a different type of record (external, smart poster, etc.), you would simply replace that line with one or more record creation methods. This is the only part of the example that might cause any headache, as it requires looking at the NDEF Forum specifications to correctly format the record to the type you want to use.

The 4 Crucial Elements to Remember

Detect the tag – enable NFC and register your app to receive an intent
Connect to the tag – use ndef.connect() outside of the main thread in case there is a problem or delay
Write to the tag – create an NDEF message with the records of your choice, and write them after connecting
Close the connection – if a connection was opened, make sure to close it whether or not a write was successful

Comments are closed.