Android NFC Programming Guide

From Intent Filters to NDEF

Android NFC Programming Guide

Android has supported NFC since API level 10 (Gingerbread) and provides a mature, well-documented NFC stack. This guide covers the essential APIs, intent filters, and foreground dispatch patterns needed to read and write tags in a production Android app.

Android NFC API Overview

The core classes live in android.nfc and android.nfc.tech:

Class Purpose
NfcAdapter Entry point; check availability, enable/disable foreground dispatch
NdefMessage Container for one or more NdefRecords
NdefRecord Single record: TNF + type + ID + payload
Tag Represents a discovered physical tag
Ndef NDEF operations on a Tag (read, write, makeReadOnly)
IsoDep ISO 14443-4 transport (needed for DESFire / nfcip-1)

Declare permissions in AndroidManifest.xml:

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

Foreground Dispatch

Foreground dispatch gives your activity priority over the system NFC dispatcher while it is in the foreground — essential for readers that must handle tags while your app is open.

class MainActivity : AppCompatActivity() {
    private lateinit var nfcAdapter: NfcAdapter
    private lateinit var pendingIntent: PendingIntent

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        nfcAdapter = NfcAdapter.getDefaultAdapter(this)
        pendingIntent = PendingIntent.getActivity(
            this, 0,
            Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
            PendingIntent.FLAG_MUTABLE
        )
    }

    override fun onResume() {
        super.onResume()
        nfcAdapter.enableForegroundDispatch(this, pendingIntent, null, null)
    }

    override fun onPause() {
        super.onPause()
        nfcAdapter.disableForegroundDispatch(this)
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        if (intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED) {
            val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
            val ndefMessage = rawMessages?.get(0) as? NdefMessage
            ndefMessage?.records?.forEach { record -> processRecord(record) }
        }
    }
}

NDEF Intent Filters

For background launch (without foreground dispatch), declare intent filters in the manifest. The system dispatches in priority order: ACTION_NDEF_DISCOVEREDACTION_TECH_DISCOVEREDACTION_TAG_DISCOVERED.

<intent-filter>
    <action android:name="android.nfc.action.NDEF_DISCOVERED" />
    <category android:name="android.intent.category.DEFAULT" />
    <data android:scheme="https" android:host="nfcfyi.com" />
</intent-filter>

This filter triggers only when a tag contains an ndef-message with an ndef-uri pointing to https://nfcfyi.com/*. Use android:mimeType instead of android:scheme for MIME-type records.

Reading and Writing Records

fun writeUrl(tag: Tag, url: String) {
    val record = NdefRecord.createUri(Uri.parse(url))
    val message = NdefMessage(arrayOf(record))
    val ndef = Ndef.get(tag) ?: return
    ndef.connect()
    ndef.writeNdefMessage(message)
    ndef.close()
}

For nfc-a (Type 2) tags like NTAG, the Ndef tech handles formatting automatically if the tag is blank. For ISO 14443-4 (card-emulation-mode or DESFire), use IsoDep and exchange raw APDUs.

Host Card Emulation (HCE)

hce lets an Android app emulate a contactless card without a hardware secure-element. Extend HostApduService, register an AID in the manifest, and respond to processCommandApdu(). HCE is the backbone of Google Pay's software-based payment path.

Use the NDEF Message Encoder to prototype message structures, and the NDEF Message Decoder to inspect raw payloads captured during development.

See also: iOS Core NFC Programming Guide and Web NFC API Guide.

Terms in This Guide