What You'll Learn
NFC reading (NDEF read/write, tag detection, foreground dispatch, Compose UI integration) explained.
Manifest Configuration
<manifest>
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="true" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
</manifest>
NfcManager
class NfcManager(private val activity: ComponentActivity) {
private val nfcAdapter: NfcAdapter? = NfcAdapter.getDefaultAdapter(activity)
val isNfcAvailable: Boolean get() = nfcAdapter != null
val isNfcEnabled: Boolean get() = nfcAdapter?.isEnabled == true
private val _tagData = MutableSharedFlow<NfcTagData>(replay = 1)
val tagData: SharedFlow<NfcTagData> = _tagData
fun enableForegroundDispatch() {
val intent = Intent(activity, activity::class.java).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
val pendingIntent = PendingIntent.getActivity(activity, 0, intent, PendingIntent.FLAG_MUTABLE)
val filters = arrayOf(IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply {
addDataType("*/*")
})
nfcAdapter?.enableForegroundDispatch(activity, pendingIntent, filters, null)
}
fun disableForegroundDispatch() {
nfcAdapter?.disableForegroundDispatch(activity)
}
fun handleIntent(intent: Intent) {
if (intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED ||
intent.action == NfcAdapter.ACTION_TAG_DISCOVERED) {
val tag = if (Build.VERSION.SDK_INT >= 33) {
intent.getParcelableExtra(NfcAdapter.EXTRA_TAG, Tag::class.java)
} else {
@Suppress("DEPRECATION") intent.getParcelableExtra(NfcAdapter.EXTRA_TAG)
}
val messages = if (Build.VERSION.SDK_INT >= 33) {
intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, NdefMessage::class.java)
} else {
@Suppress("DEPRECATION") intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
}
val records = messages?.flatMap { (it as NdefMessage).records.toList() } ?: emptyList()
val textContent = records.mapNotNull { record ->
if (record.tnf == NdefRecord.TNF_WELL_KNOWN) String(record.payload, Charsets.UTF_8)
else null
}
_tagData.tryEmit(NfcTagData(
id = tag?.id?.toHexString() ?: "",
techList = tag?.techList?.toList() ?: emptyList(),
content = textContent
))
}
}
}
data class NfcTagData(
val id: String,
val techList: List<String>,
val content: List<String>
)
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
Compose Screen
@Composable
fun NfcReaderScreen(nfcManager: NfcManager) {
val tagData by nfcManager.tagData.collectAsStateWithLifecycle(initialValue = null)
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_RESUME -> nfcManager.enableForegroundDispatch()
Lifecycle.Event.ON_PAUSE -> nfcManager.disableForegroundDispatch()
else -> {}
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
}
Column(
Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (!nfcManager.isNfcAvailable) {
Text("This device doesn't support NFC", color = MaterialTheme.colorScheme.error)
} else if (!nfcManager.isNfcEnabled) {
Text("NFC is disabled. Enable it in settings")
} else {
Icon(Icons.Default.Nfc, null, Modifier.size(64.dp), tint = MaterialTheme.colorScheme.primary)
Text("Tap an NFC tag", style = MaterialTheme.typography.headlineSmall)
}
tagData?.let { data ->
Spacer(Modifier.height(24.dp))
Card(Modifier.fillMaxWidth()) {
Column(Modifier.padding(16.dp)) {
Text("Tag ID: ${data.id}", style = MaterialTheme.typography.titleMedium)
Text("Tech: ${data.techList.joinToString()}", style = MaterialTheme.typography.bodySmall)
data.content.forEach { Text("Content: $it") }
}
}
}
}
}
Summary
| Feature | Implementation |
|---|---|
| Tag detection | enableForegroundDispatch |
| NDEF reading | NdefMessage.records |
| NFC state | NfcAdapter.isEnabled |
| Compose integration |
SharedFlow + collectAsState
|
-
enableForegroundDispatchdetects tags when app is foreground - Extract text/URI/MIME data from
NdefRecord - Auto-switch on Lifecycle resume/pause
- Display NFC availability properly in UI
Ready-Made Android App Templates
8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.
Browse templates → Gumroad
Top comments (0)