Android Jetpack Compose: Transmitting NFC-Read Data to Screens Using Broadcast

Alparslan Güney
Asis Technologies

--

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

--

--