Use MiFare contactless card on a device
With the Clover Android SDK, your app can interact with non-financial MiFare contactless cards on a Clover device. This tutorial walks you through using the MiFare APIs in a sample app to connect to the service, read a card, and handle the result.
Prerequisites
To use the MiFare APIs, you must configure your project and ensure your test environment meets the minimum requirements.
System requirements
Clover devices: Flex 4, Flex Pocket, Flex 3, Mini 3, Kiosk, Station Duo 2. This feature is only available for the Latin America region.
ROM: Q2 2025 release or later.
Core Payments App: Version released on or after March 31, 2025.
Supported MiFare Cards:
- MiFare Classic 1K / 4K
- Read UID & retrieve data
- Write data
- MiFare Ultralight
- Read UID & retrieve data
- MiFare Ultralight EV1
- Read UID & retrieve data
Limitations
Secure access module (SAM) not supported: Clover devices do not have a built-in SAM slot. For use cases that require interaction with a SAM card, you must use an external card reader.
Build configuration
The Clover Android SDK is published on Maven Central. Therefore, you must add that repository and the dependencies to your build configurations by performing the following steps:
- Open your project-level
build.gradle
file. - In the repositories block, add
mavenCentral()
as a repository.
repositories {
mavenCentral()
google()
}
- Open your module-level
build.gradle
file. - In the
dependencies
block, add the following implementation statement for the MiFare-compatible SDK version:
dependencies {
...
implementation "com.clover.sdk:clover-android-sdk:323"
}
1. Connect to the MiFare service
Get an instance of the MifareCardReaderClient
and establish a connection. This requires an IMifareCardReaderClientListener
to receive onConnected
and onDisconnect
callbacks.
Important: All card operations must be completed within a 30-second timeout period. If an operation exceeds this limit, the client disconnects, and you need to reconnect.
import com.clover.sdk.v3.mifare.connector.IMifareCardReaderClientListener
import com.clover.sdk.v3.mifare.connector.MifareCardReaderClient
private var mifareCardReaderClient: MifareCardReaderClient? = null
private fun connectToMifareService() {
mifareCardReaderClient = MifareCardReaderClient.getInstance(applicationContext)
mifareCardReaderClient?.connect(object : IMifareCardReaderClientListener {
override fun onConnected() {
// Service is connected. Ready to perform card operations.
}
override fun onDisconnect() {
// Service is disconnected.
}
})
}
2. Read Card ID
After the nConnected()
callback fires, you can retrieve the card's unique identifier (ID). This operation does not require an authentication key.
import com.clover.sdk.v3.mifare.*
import com.clover.sdk.v3.mifare.connector.MifareCardReaderException
fun readCardID() {
try {
// 1. Call the read ID method
val result: MifareCard? = mifareCardReaderClient?.cardUIDRead()
// 2. Handle the result
if (result != null) {
// Success: process the result.uid
} else {
// Handle null response
}
} catch (e: MifareCardReaderException) {
// Handle exception
}
}
3. Read a MiFare Classic card
After the onConnected()
callback fires, you can perform card operations. Reading a MiFare Classic card requires providing a key for authentication.
Before creating a key, it's important to understand the MifareCardKey
parameters:
keyType
: Specifies whether to use Key A (0x0A
) or Key B (0x0B
) for authentication.keyNum
: Slot number (1-8) in the device's secure keystore where the key will be loaded.keyVersion
: Integer representing the version of the key, for management purposes.keyData
: 6-byte key as a hexadecimal string.
Alternative key approach
An alternative approach may be available depending on the device ROM version. This method allows you to load both Key A and Key B in a single call using a 12-byte key string. The first 6 bytes are treated as Key A and the second 6 bytes as Key B. The keyType parameter determines which half of the key is used for the operation.
mifareCardKey = MifareCardKey(0x0A, 0, 1, "B98950CD89AAC9F950CD89BB")
import com.clover.sdk.v3.mifare.*
import com.clover.sdk.v3.mifare.connector.MifareCardReaderException
fun readMiFareClassicCard() {
try {
// 1. Create the key for authentication
val cardKey = MifareCardKey(0x0A, 0, 1, "FFFFFFFFFFFF")
// 2. Build the read request
val request = MifareCardDataRequest(4, 3, cardKey)
// This request reads 3 blocks, starting from absolute block 4.
// On a MiFare 1K card, this corresponds to Sector 1, blocks 0, 1, and 2.
// 3. Call the read method
val result: MifareCard? = mifareCardReaderClient?.cardClassicRead(request)
// 4. Handle the result
if (result != null) {
// Success: process the result.cardData
} else {
// Handle null response
}
} catch (e: MifareCardReaderException) {
// Handle exception
}
}
Key Management
Key Slots:
The Clover device's secure keystore has a maximum of 8 slots available for loading MiFare keys. You must manage which key is loaded into each slot (keyNum
).
Key Types:
Ensure you are using the correct keyType
as defined for the Clover SDK (for example, 0x0A
for Key A and 0x0B
for Key B), as this may differ from other native Android implementations.
4. Write to a MiFare Classic card
Writing data to a card follows a similar process to reading it. You must provide the data to be written as a hexadecimal string in the MifareCardDataRequest
.
import com.clover.sdk.v3.mifare.*
import com.clover.sdk.v3.mifare.connector.MifareCardReaderException
fun writeMiFareClassicCard() {
try {
// 1. Create the key for authentication
val cardKey = MifareCardKey(0x0A, 0, 1, "FFFFFFFFFFFF")
// 2. Define the data to write (must be a hex string)
val dataToWrite = "01020304050607080910111213141516"
// 3. Build the write request
val request = MifareCardDataRequest(4, 3, cardKey, dataToWrite)
// This request writes to 3 blocks, starting from absolute block 4.
// On a MiFare 1K card, this corresponds to Sector 1, blocks 0, 1, and 2.
// 4. Call the write method
val success: Boolean? = mifareCardReaderClient?.cardClassicWrite(request)
// 5. Handle the result
if (success == true) {
// Write operation was successful
} else {
// Write operation failed
}
} catch (e: MifareCardReaderException) {
// Handle exception
}
}
5. Disconnect from the service
When your application completes its operations, call the disconnect()
method to release the service. To interrupt an operation that is already in progress, call the cancel()
method.
fun cleanup() {
// Disconnect after a completed operation
mifareCardReaderClient?.disconnect()
}
fun interruptOperation() {
// Cancel an in-progress operation
mifareCardReaderClient?.cancel()
}
Updated about 7 hours ago