Learn Android NFC Basics by Building a Simple Messenger

Share this article

This article was peer reviewed by Tim Severien. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!


NFC (Near Field Communication) is a short range wireless method of communication between devices or between a device and an NFC ‘tag’. NFC is not limited to Android or mobile devices in general, but this tutorial is specific to the Android implementation of NFC.

By the end of this tutorial you will understand the basic concepts of NFC as well as how to set up basic communication between Android devices. You will need to have an API of 14 or higher to complete this tutorial. Although some functions introduced in API 16 are used, they are convenience functions and not required.

You can find the complete code for this tutorial on GitHub.

Formatting for NFC

NFC has a general standard created by the NFC Forum to make sure that the interface can work across different systems. This format is ‘NDEF’ (NFC Data Exchange Format) and allows us to know how information in tags is likely presented to us, and gives a way to ensure that data we create can be useful to the largest possible number of users.

For now, this is all I’ll say about formatting, but we’ll come back to it.

The Tag Dispatch System

The Android OS handles NFC through its ‘NFC Tag Dispatch System.’ This is a part of the system separate from your application that you have little control over. It’s constantly looking (assuming NFC is not disabled on the device) for NFC devices it can interface with. If the device comes within 4 centimeters of another NFC enabled device or an NFC tag the system will dispatch an intent and this is how we receive data.

Open Android Studio and create a project with a blank activity and we’ll get started.

Filtering for NFC Intents

When filtering for NFC intents you want to be as specific as you can. This is to avoid the chooser dialog appearing for apps that can handle the intent. Normally, the chooser is no problem, and often it’s the preferred behavior to let it (or force it to) show, but this is not the case for NFC.

NFC requires devices to be within centimeters of each other. If we allow the chooser to show, our user will likely move the device back to themselves to look and cancel the interaction.

The tag dispatch system has three actions it can attach to the intent it created in response to finding something it can read, write, or communicate with. The dispatch system will send out several intents, if one intent fails to find an activity to handle it the next in the list is sent.

The actions that the Tag Dispatch System will attach to its intents are:

  1. ACTION_NDEF_DISCOVERED – Sent if the information found is formatted as NDEF.
  2. ACTION_TECH_DISCOVERED – Sent if the first fails, or if the data was formatted in an unfamiliar way
  3. ACTION_TAG_DISCOVERED – The last and most general. Remember, we want to capture the intent before this, as it’s likely we will have multiple activities that have specified something this general.

We’re going to be creating a simple messenger to send and receive a list of strings.

Open AndroidManifest.xml and add the following intent filter to the main activity:

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

As with any Android project we’re going to have to ask for the appropriate permissions. Add the following permission and feature to AndroidManifest.xml:

<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true"/>

To make things smoother later, set the launch mode for the main activity to ‘single task’. This will allow us to handle intents sent to our activity without having to recreate the activity, giving a more fluid feel to our user.

<activity
           android:launchMode="singleTask"
           android:name=".MainActivity"
           android:label="@string/app_name" >

Simple Interface

We need a way to add messages to send to an array of strings. You can create your own method, or use the simple interface I have below.

Change activity_main.xml to:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

   <EditText
       android:id="@+id/txtBoxAddMessage"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />

   <Button
       android:id="@+id/buttonAddMessage"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:onClick="addMessage"
       android:layout_below="@+id/txtBoxAddMessage"
       android:layout_centerHorizontal="true" />

   <TextView
       android:id="@+id/txtMessagesReceived"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_below="@+id/buttonAddMessage"
       android:layout_alignParentEnd="true"
       android:layout_alignParentRight="true"/>


   <TextView
       android:id="@+id/txtMessageToSend"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignTop="@+id/txtMessagesReceived"
       android:layout_alignParentLeft="true"
       android:layout_alignParentStart="true"/>

</RelativeLayout>

Update MainActivity.java to the following:

public class MainActivity extends AppCompatActivity {
    //The array lists to hold our messages
    private ArrayList<String> messagesToSendArray = new ArrayList<>();
    private ArrayList<String> messagesReceivedArray = new ArrayList<>();

    //Text boxes to add and display our messages
    private EditText txtBoxAddMessage;
    private TextView txtReceivedMessages;
    private TextView txtMessagesToSend;

    public void addMessage(View view) {
        String newMessage = txtBoxAddMessage.getText().toString();
        messagesToSendArray.add(newMessage);

        txtBoxAddMessage.setText(null);
        updateTextViews();

        Toast.makeText(this, "Added Message", Toast.LENGTH_LONG).show();
    }


    private  void updateTextViews() {
        txtMessagesToSend.setText("Messages To Send:\n");
        //Populate Our list of messages we want to send
        if(messagesToSendArray.size() > 0) {
            for (int i = 0; i < messagesToSendArray.size(); i++) {
                txtMessagesToSend.append(messagesToSendArray.get(i));
                txtMessagesToSend.append("\n");
            }
        }

        txtReceivedMessages.setText("Messages Received:\n");
        //Populate our list of messages we have received
        if (messagesReceivedArray.size() > 0) {
            for (int i = 0; i < messagesReceivedArray.size(); i++) {
                txtReceivedMessages.append(messagesReceivedArray.get(i));
                txtReceivedMessages.append("\n");
            }
        }
    }

    //Save our Array Lists of Messages for if the user navigates away
    @Override
    public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putStringArrayList("messagesToSend", messagesToSendArray);
        savedInstanceState.putStringArrayList("lastMessagesReceived",messagesReceivedArray);
    }

    //Load our Array Lists of Messages for when the user navigates back
    @Override
    public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        messagesToSendArray = savedInstanceState.getStringArrayList("messagesToSend");
        messagesReceivedArray = savedInstanceState.getStringArrayList("lastMessagesReceived");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        txtBoxAddMessage = (EditText) findViewById(R.id.txtBoxAddMessage);
        txtMessagesToSend = (TextView) findViewById(R.id.txtMessageToSend);
        txtReceivedMessages = (TextView) findViewById(R.id.txtMessagesReceived);
        Button btnAddMessage = (Button) findViewById(R.id.buttonAddMessage);

        btnAddMessage.setText("Add Message");
        updateTextViews();
    }
}

Checking for NFC Support

In the MainActivity.java onCreate() method add the following to handle when NFC is not supported on the device:

//Check if NFC is available on device
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(mNfcAdapter != null) {
    //Handle some NFC initialization here
  }
  else {
      Toast.makeText(this, "NFC not available on this device",
                Toast.LENGTH_SHORT).show();
  }

Make sure to create the mNfcAdapter variable at the top of the class definition:

private NfcAdapter mNfcAdapter;

Creating Our Message

Android provides useful classes and functions that allow us to package our data. To conform to NDEF, we can create NdefMessages which contain one or more NdefRecords.

To send a message, we have to create it first. There are two main ways to handle this:

  1. Call setNdefPushMessage() in the NfcAdapter class. This will accept an NdefMessage sent when detecting another NFC capable device.
  2. Override callbacks so that our NdefMessage will be created only when it needs to be sent.

Number one is the preferred method if the data will not change. Our data will be changing, so we’ll use option number two.

To handle message sending we need to specify callbacks to respond to NFC events. Since we will be updating our data we want the message to be created only when it needs to be sent.

Update the activity like so:

public class MainActivity extends Activity
       implements NfcAdapter.OnNdefPushCompleteCallback,
                  NfcAdapter.CreateNdefMessageCallback

Override the relevant functions:

@Override
public void onNdefPushComplete(NfcEvent event) {
    //This is called when the system detects that our NdefMessage was
    //Successfully sent.
    messagesToSendArray.clear();
}

@Override
public NdefMessage createNdefMessage(NfcEvent event) {
    //This will be called when another NFC capable device is detected.
    if (messagesToSendArray.size() == 0) {
        return null;
    }
    //We'll write the createRecords() method in just a moment
    NdefRecord[] recordsToAttach = createRecords();
    //When creating an NdefMessage we need to provide an NdefRecord[]
    return new NdefMessage(recordsToAttach);
}

Now make sure to specify these callbacks in the onCreate method:

//Check if NFC is available on device
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if(mNfcAdapter != null) {
    //This will refer back to createNdefMessage for what it will send
    mNfcAdapter.setNdefPushMessageCallback(this, this);

    //This will be called if the message is sent successfully
    mNfcAdapter.setOnNdefPushCompleteCallback(this, this);
}

Creating the Records

There are multiple utility functions within the NdefRecord class that can return a properly formatted NdefRecord, but to understand the concept and give us more flexibility we’re going to manually create a NdefRecord first.

In NDEF there are four parts to a record:

  1. A short that specifies the type name of our payload from a list of constants.
  2. A variable length byte[] that gives more detail about our type.
  3. A variable length byte[] used as a unique identifier. This is neither required or often used.
  4. A variable length byte[] that is our actual payload

Add this function to MainActivity.java:

public NdefRecord[] createRecords() {

    NdefRecord[] records = new NdefRecord[messagesToSendArray.size()];

    for (int i = 0; i < messagesToSendArray.size(); i++){

        byte[] payload = messagesToSendArray.get(i).
           getBytes(Charset.forName("UTF-8"));

        NdefRecord record = new NdefRecord(
                NdefRecord.TNF_WELL_KNOWN,  //Our 3-bit Type name format
                NdefRecord.RTD_TEXT,        //Description of our payload
                new byte[0],                //The optional id for our Record
                payload);                   //Our payload for the Record

        records[i] = record;
    }
    return records;
}

Since we’re writing both the sender and receiver we can be specific about how we want our data handled. We can call NdefRecord.createApplicationRecord to attach a specially formatted NdefRecord that will tell the OS which application should handle the data. The system will attempt to open the application to handle the data before any other.

It doesn’t matter where in the NdefRecord[] array we include this record, as long as it’s present anywhere it will work. Make sure to adjust the length of our NdefRecord[] to be one longer to accommodate the additional record and add the following before the return in the createRecords() function.

//Remember to change the size of your array when you instantiate it.
records[messagesToSendArray.size()] =
           NdefRecord.createApplicationRecord(getPackage());

An advantage of creating and attaching an Android Application Record is that if Android cannot find the application it will open a connection to the Google Play store and attempt to download your application (assuming it exists).

Note: This doesn’t make the transaction secure or ensure that your app will be the one to open it. Including the application record only further specifies our preference to the OS. If another activity that is currently in the foreground calls NfcAdapter.enableForegroundDispatch it can catch the intent before it gets to us, there is no way to prevent this except to have our activity in the foreground. Still, this is as close as we can get to ensuring that our application is the one that processes this data.

As mentioned, it’s generally preferred to use the provided utility functions to create the Records. Most of these functions were introduced in API 16, and we are writing for 14 or higher. So we cover all bases, let’s include a check for the API level and create our record in the preferred manner if the function is available to us. Change the createRecords() function to this:

public NdefRecord[] createRecords() {
    NdefRecord[] records = new NdefRecord[messagesToSendArray.size() + 1];
        //To Create Messages Manually if API is less than
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
            for (int i = 0; i < messagesToSendArray.size(); i++){
               byte[] payload = messagesToSendArray.get(i).
                             getBytes(Charset.forName("UTF-8"));
               NdefRecord record = new NdefRecord(
                       NdefRecord.TNF_WELL_KNOWN,      //Our 3-bit Type name format
                       NdefRecord.RTD_TEXT,            //Description of our payload
                       new byte[0],                    //The optional id for our Record
                       payload);                       //Our payload for the Record

               records[i] = record;
           }
       }
       //Api is high enough that we can use createMime, which is preferred.
       else {
           for (int i = 0; i < messagesToSendArray.size(); i++){
               byte[] payload = messagesToSendArray.get(i).
                                getBytes(Charset.forName("UTF-8"));

               NdefRecord record = NdefRecord.createMime("text/plain",payload);
               records[i] = record;
           }
       }
      records[messagesToSendArray.size()] =
       NdefRecord.createApplicationRecord(getPackageName());
       return records;
  }

Processing the Message

The intent received will contain an NdefMessage[] array. Since we know the length, it’s easy to process.

private void handleNfcIntent(Intent NfcIntent) {
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(NfcIntent.getAction())) {
            Parcelable[] receivedArray =
                NfcIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

        if(receivedArray != null) {
            messagesReceivedArray.clear();
            NdefMessage receivedMessage = (NdefMessage) receivedArray[0];
            NdefRecord[] attachedRecords = receivedMessage.getRecords();

            for (NdefRecord record:attachedRecords) {
                String string = new String(record.getPayload());
                //Make sure we don't pass along our AAR (Android Application Record)
                if (string.equals(getPackageName())) { continue; }
                messagesReceivedArray.add(string);
            }
            Toast.makeText(this, "Received " + messagesReceivedArray.size() +
                    " Messages", Toast.LENGTH_LONG).show();
            updateTextViews();
        }
        else {
            Toast.makeText(this, "Received Blank Parcel", Toast.LENGTH_LONG).show();
        }
    }
}


@Override
public void onNewIntent(Intent intent) {
        handleNfcIntent(intent);
}

We are overriding onNewIntent so we can receive and process the message without creating a new activity. It’s not necessary but will help make everything feel fluid. Add a call to handleNfcIntent in the onCreate() and onResume() functions to be sure that all cases are handled.

@Override
  public void onResume() {
      super.onResume();
      updateTextViews();
      handleNfcIntent(getIntent());
  }

That’s it! You should have a simple functioning NFC messenger. Attaching different types of files is as easy as specifying a different mime type and attaching the binary of the file you want to send. For a full list of supported types and their convenience constructors take a look at the NdefMessage and NdefRecord classes in the Android documentation. More complex features are available on Android with NFC such as emulating an NFC tag so that we can passively read, but that is beyond a simple messenger application.

Frequently Asked Questions (FAQs) about Android NFC Basics

What is NFC and how does it work on Android?

NFC, or Near Field Communication, is a technology that allows wireless communication between two devices that are in close proximity to each other, typically within a few centimeters. On Android, NFC is used for a variety of purposes, such as contactless payments, data transfer, and reading or writing to NFC tags. When two NFC-enabled devices are brought close together, they establish a connection and can exchange data. This is done through electromagnetic radio fields, allowing for quick and easy communication.

How can I check if my Android device supports NFC?

To check if your Android device supports NFC, go to Settings > More. If NFC is listed, then your device supports it. You can also look for a small NFC logo on the back of your device. If your device does not support NFC, you will not be able to use NFC-based features such as contactless payments or data transfer.

How can I enable or disable NFC on my Android device?

To enable or disable NFC on your Android device, go to Settings > More > NFC. Here, you can toggle the switch to enable or disable NFC. Keep in mind that NFC consumes power, so you may want to turn it off when not in use to save battery life.

What is an NFC tag and how can I use it with my Android device?

An NFC tag is a small device that can store data, such as a URL or a text message. When an NFC-enabled device, like an Android phone, is brought close to the tag, it can read the data stored on it. This can be used for a variety of purposes, such as launching a specific app or opening a website. To use an NFC tag with your Android device, simply bring your device close to the tag to read the data.

Can I use NFC for contactless payments on my Android device?

Yes, you can use NFC for contactless payments on your Android device. This is done through apps like Google Pay, which allow you to store your credit or debit card information on your device and use it to make payments at NFC-enabled payment terminals. To make a payment, simply unlock your device and hold it near the payment terminal.

How can I develop an Android app that uses NFC?

Developing an Android app that uses NFC requires knowledge of the Android SDK and the NFC API. The NFC API provides classes and interfaces that allow you to read and write to NFC tags, establish peer-to-peer connections, and perform other NFC-related tasks. You can find more information on how to use the NFC API in the Android developer documentation.

What is peer-to-peer mode in NFC?

Peer-to-peer mode in NFC is a mode that allows two NFC-enabled devices to communicate with each other. This can be used for a variety of purposes, such as exchanging data or playing multiplayer games. To establish a peer-to-peer connection, simply bring the two devices close to each other.

What is the range of NFC?

The range of NFC is typically up to a few centimeters. This is because NFC uses magnetic field induction to enable communication, which requires the devices to be in close proximity. However, the exact range can vary depending on the devices and the environment.

Is NFC secure?

Yes, NFC is generally considered secure. This is because the short range of NFC makes it difficult for unauthorized devices to intercept the communication. In addition, many NFC-enabled apps and services use encryption to further secure the data.

Can I use NFC to connect to Wi-Fi networks?

Yes, you can use NFC to connect to Wi-Fi networks. This is done through a feature called Wi-Fi Protected Setup (WPS), which allows you to connect to a Wi-Fi network by simply bringing your device close to the router. However, not all routers support this feature, so check with your router’s manufacturer for more information.

Ethan DamschroderEthan Damschroder
View Author

Freelance mobile developer.

androidchriswmessagingnfc
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week