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 14443ISO 14443Standard for contactless smart cards at 13.56 MHz (Types A and B)View full →-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_DISCOVERED → ACTION_TECH_DISCOVERED → ACTION_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.