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