Android Jetpack Compose: Transmitting NFC-Read Data to Screens Using Broadcast
Hello, there isn’t much information available on how to handle NFC readings on the UI side with Android Jetpack Compose. Therefore, I’ll share the method I’ve been using. This method essentially involves transmitting NFC readings to screens using broadcast in a straightforward manner. The first thing we need to do is create an NfcAdapter in the activity side. Of course, don’t forget to add the NFC permission to the manifest file beforehand.
To create NfcAdapter;
class MainActivity : ComponentActivity() {
private var nfcAdapter : NfcAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
setContent {
ComposeNFCTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting()
}
}
}
}
}
Next, we need to activate NFC reading. For this, we write the following function within the activity and call it inside onResume. Of course, this part can vary depending on the use case, but for now, I didn’t want to make it complicated.
private fun enableNfcForegroundDispatch() {
nfcAdapter?.let { adapter ->
if (adapter.isEnabled) {
val nfcIntentFilter = arrayOf(
IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED),
IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
)
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getActivity(
this,
0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_MUTABLE
)
} else {
PendingIntent.getActivity(
this,
0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_UPDATE_CURRENT
)
}
adapter.enableForegroundDispatch(
this, pendingIntent, nfcIntentFilter, null
)
}
}
}
override fun onResume() {
super.onResume()
enableNfcForegroundDispatch()
}
The reason we call it inside onResume is to disable NFC reading when the activity is paused and resume reading again when the activity is resumed. Therefore, we also write a method named disableNfcForegroundDispatch
and call it inside the onPause
method. When a card is read, the onNewIntent
method that we override in the activity will be called. The body of this method is currently empty, and we will fill it in when we create the broadcast. Here is the final code:
class MainActivity : ComponentActivity() {
private var nfcAdapter : NfcAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
setContent {
ComposeNFCTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting()
}
}
}
}
private fun enableNfcForegroundDispatch() {
nfcAdapter?.let { adapter ->
if (adapter.isEnabled) {
val nfcIntentFilter = arrayOf(
IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED),
IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED),
IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED)
)
val pendingIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PendingIntent.getActivity(
this,
0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_MUTABLE
)
} else {
PendingIntent.getActivity(
this,
0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_UPDATE_CURRENT
)
}
adapter.enableForegroundDispatch(
this, pendingIntent, nfcIntentFilter, null
)
}
}
}
private fun disableNfcForegroundDispatch() {
nfcAdapter?.disableForegroundDispatch(this)
}
override fun onResume() {
super.onResume()
enableNfcForegroundDispatch()
}
override fun onPause() {
super.onPause()
disableNfcForegroundDispatch()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let {
// handle NFC intent
}
}
}
Now that we can perform NFC reading, the next step is to be able to transmit the read card via broadcast. For this, as mentioned in the Android developer documentation, we’ll create a broadcast receiver that can be used inside composable functions. I’m writing this in a new Kotlin file named NfcBroadcastReceiver
.
const val INTENT_ACTION_NFC_READ = "com.alparslanguney.example.nfc.util.INTENT_ACTION_NFC_READ"
@Composable
fun NfcBroadcastReceiver(
onSuccess: (Tag) -> Unit
) {
val context = LocalContext.current
val currentOnSystemEvent by rememberUpdatedState(onSuccess)
DisposableEffect(context) {
val intentFilter = IntentFilter(INTENT_ACTION_NFC_READ)
val broadcast = object : BroadcastReceiver() {
override fun onReceive(
context: Context?,
intent: Intent?
) {
intent?.getParcelableCompatibility(NfcAdapter.EXTRA_TAG, Tag::class.java)?.let { tag ->
currentOnSystemEvent(tag)
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(
broadcast, intentFilter,
Context.RECEIVER_NOT_EXPORTED
)
} else {
context.registerReceiver(broadcast, intentFilter)
}
onDispose {
context.unregisterReceiver(broadcast)
}
}
}
This code, as you can see, registers a broadcast inside a DisposableEffect
. If the component we created with Compose is removed from the screen, onDispose
will be triggered, and the broadcast will be unregistered. Now that we have created our broadcast, the next step is to use our broadcast within a composable function. All we need to do is call NfcBroadcastReceiver
inside the composable.
@OptIn(ExperimentalStdlibApi::class)
@Composable
fun Greeting(modifier: Modifier = Modifier) {
var nfcCardId by remember {
mutableStateOf("")
}
Text(text = "Read Card : $nfcCardId", modifier = modifier)
NfcBroadcastReceiver { tag ->
nfcCardId = tag.id.toHexString()
}
}
The last thing remaining is to send the read card data to this broadcast. We will do this within the onNewIntent
method using the INTENT_ACTION_NFC_READ
action that we defined as a constant in NfcBroadcastReceiver.kt
.
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.let { nfcIntent ->
sendBroadcast(Intent(INTENT_ACTION_NFC_READ).apply {
putExtra(
NfcAdapter.EXTRA_TAG,
nfcIntent.getParcelableCompatibility(NfcAdapter.EXTRA_TAG, Tag::class.java)
)
})
}
}
Everything is set up now. We can now send the NFC-read card data using broadcast.
Full code : https://github.com/alparslanguney/compose-nfc-broadcast