DEV Community

Cover image for Unlocking NFC: Reading MIFARE Cards with Jetpack Compose
supriya shah
supriya shah

Posted on

Unlocking NFC: Reading MIFARE Cards with Jetpack Compose

Originally published on Medium:
https://medium.com/@supsabhi/unlocking-nfc-reading-mifare-cards-with-jetpack-compose-bf250171d1f1

NFC (Near Field Communication) is one of those technologies we use almost daily — without even realising it. From tapping your phone at the supermarket checkout to scanning an access badge at work, NFC has quietly become a part of our everyday lives.

If you’re building an Android app that involves card reading — for public transportation, access control, loyalty programs, or event tickets — understanding how to implement NFC tag reading is essential. In this post, we’ll explore how to read MIFARE Classic tags using Android and Jetpack Compose.

Before diving into the implementation, let’s briefly talk about the types of NFC tags you may encounter. NFC Forum classifies tags into Type 1 to Type 5, and among the most popular families are:

MIFARE Classic — Found in transport cards, hotel keys, and loyalty cards. Widely used, but not the most secure.

MIFARE Ultralight — Low-cost and used for disposable tickets or event passes.

MIFARE DESFire — Designed for higher-security applications, but more expensive.

NTAG Series — Known for better security and compatibility. A solid choice for general-purpose usage.

Each has its pros and cons.Selection of card depends on your app’s needs — security, cost, and data volume.

Let’s build a basic app that can read MIFARE Classic tags. We’ll use Jetpack Compose for UI and handle NFC in a standard way.

Step 1: Add Required Permissions and Features
In your AndroidManifest.xml, declare NFC permissions and features:

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

Also, add an intent-filter and meta-data block to capture the tag:

<intent-filter>
    <action android:name="android.nfc.action.TECH_DISCOVERED" />
</intent-filter>
<meta-data
    android:name="android.nfc.action.TECH_DISCOVERED"
    android:resource="@xml/nfc_tech_filter" />
Enter fullscreen mode Exit fullscreen mode

And create a res/xml/nfc_tech_filter.xml file with:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
    <tech-list>
        <tech>android.nfc.tech.MifareClassic</tech>
    </tech-list>
</resources>
Enter fullscreen mode Exit fullscreen mode

Step 2: Capture the Tag in MainActivity
Set up the NFC adapter and foreground dispatch in your MainActivity:

class MainActivity : ComponentActivity() {
    private var nfcAdapter: NfcAdapter? = null
    private val viewModel: NfcViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        nfcAdapter = NfcAdapter.getDefaultAdapter(this)
        setContent {
            NfcScreen(viewModel)
        }
    }
override fun onResume() {
        super.onResume()
        val intent = Intent(this, javaClass).apply {
            addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
        }
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE)
        nfcAdapter?.enableForegroundDispatch(this, pendingIntent, null, null)
    }
    override fun onPause() {
        super.onPause()
        nfcAdapter?.disableForegroundDispatch(this)
    }
    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        val tag = intent.getParcelableExtra<Tag>(NfcAdapter.EXTRA_TAG)
        tag?.let {
            val result = readMifareClassic(it)
            viewModel.updateNfcData(result)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Read MIFARE Classic Data
This function reads data from each block of a MIFARE Classic tag:

fun readMifareClassic(tag: Tag): String {
    val mfc = MifareClassic.get(tag) ?: return "Not a MIFARE Classic tag"
    val sb = StringBuilder()
try {
        mfc.connect()
        val sectorCount = mfc.sectorCount
        for (sector in 0 until sectorCount) {
            val auth = mfc.authenticateSectorWithKeyA(sector, MifareClassic.KEY_DEFAULT)
            if (auth) {
                val blockCount = mfc.getBlockCountInSector(sector)
                val blockIndex = mfc.sectorToBlock(sector)
                for (block in 0 until blockCount) {
                    val data = mfc.readBlock(blockIndex + block)
                    sb.append("Sector $sector Block $block: ${data.joinToString(" ") { "%02X".format(it) }}\n")
                }
            } else {
                sb.append("Sector $sector: Authentication failed\n")
            }
        }
    } catch (e: IOException) {
        sb.append("Error: ${e.localizedMessage}")
    } finally {
        try { mfc.close() } catch (_: IOException) {}
    }
    return sb.toString()
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Store and Display the Data Using ViewModel + Compose
We’ll use a ViewModel to hold the NFC data and update the UI reactively.

class NfcViewModel : ViewModel() {
    var nfcData by mutableStateOf("Scan a MIFARE Classic tag")
        private set
fun updateNfcData(data: String) {
        nfcData = data
    }
}

Enter fullscreen mode Exit fullscreen mode

The Jetpack Compose UI:

@Composable
fun NfcScreen(viewModel: NfcViewModel) {
    val nfcData = viewModel.nfcData
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Scan MIFARE Tag", fontSize = 24.sp)
        Spacer(modifier = Modifier.height(16.dp))
        Text(text = nfcData, fontSize = 18.sp, color = Color.DarkGray)
    }
}
Enter fullscreen mode Exit fullscreen mode

That’s it — you now have a working Android app that can scan and read MIFARE Classic NFC tags using Jetpack Compose!

This is just the foundation. You can expand this to:Write to NFC tags,Authenticate with custom keys,Handle different tag types (Ultralight, DESFire, NTAG),Encrypt/decrypt data blocks

Final Thoughts
While MIFARE Classic is commonly used, it’s worth noting that it has some known security weaknesses. For sensitive data, consider more secure alternatives like MIFARE DESFire or NTAG cards. Always choose your NFC tech based on the use-case.

Whether you’re building an access control system or creating an innovative event app, integrating NFC can take your user experience to the next level.

Let me know in the comments if you’d like a follow-up post on writing to NFC tags ?

Top comments (0)