Android card scanner library for detecting card numbers, expiry dates, and cardholder names using CameraX and Google ML Kit.
Card Scanner provides a configurable Android scanning experience built with Kotlin, Jetpack Compose, CameraX, ML Kit, and Hilt.
- Card number detection using ML Kit OCR
- Luhn validation for detected card numbers
- Expiry date recognition
- Cardholder name detection
- English language support
- Arabic language support
- Light theme
- Dark theme
- System theme
- Custom scanner colors
- Torch control
- Camera focus support
- Exposure control
- Multi-frame scan stabilization
- Jetpack Compose UI
- R8 / ProGuard support
| scanner | scanner data |
|---|---|
![]() | ![]() |
| Requirement | Version |
|---|---|
| Minimum Android SDK | 29 |
| Compile SDK | 37+ |
| Java | 17 |
| Kotlin | Supported Android Kotlin version |
Add Maven Central to your repositories:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}Then add Card Scanner:
dependencies {
implementation("io.github.mohamedebrahem13:card-scanner:1.0.0")
}Add the following permission to your AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />The application using the library must request camera permission at runtime before opening the scanner.
private val cardScannerLauncher =
registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
val cardData =
CardScanner.parseActivityResult(result)
if (cardData != null) {
val cardNumber =
cardData.cardNumber
val expiryDate =
cardData.expiryDate
val cardHolderName =
cardData.cardHolderName
}
}val config =
CardScannerConfig(
themeMode = CardScannerThemeMode.SYSTEM,
language = CardScannerLanguage.ENGLISH,
scanStabilizationMs = 400L,
minCardNumberDetections = 2,
requireExpiryDate = false
)cardScannerLauncher.launch(
CardScanner.createIntent(
this,
config
)
)Card Scanner supports different languages, themes, and custom colors.
val config =
CardScannerConfig(
themeMode =
CardScannerThemeMode.LIGHT,
language =
CardScannerLanguage.ENGLISH,
lightColors =
CardScannerColorScheme
.lightDefaults()
.copy(
frameColor = "#FFD32F2F",
activeControlBackgroundColor = "#FFD32F2F",
activeControlContentColor = "#FFFFFFFF"
),
scanStabilizationMs = 400L,
minCardNumberDetections = 2,
requireExpiryDate = false
)val config =
CardScannerConfig(
themeMode =
CardScannerThemeMode.DARK,
language =
CardScannerLanguage.ENGLISH,
darkColors =
CardScannerColorScheme
.darkDefaults()
.copy(
frameColor = "#FFFF5252",
activeControlBackgroundColor = "#FFFF5252",
activeControlContentColor = "#FF000000"
),
scanStabilizationMs = 400L,
minCardNumberDetections = 2,
requireExpiryDate = false
)val config =
CardScannerConfig(
themeMode =
CardScannerThemeMode.LIGHT,
language =
CardScannerLanguage.ARABIC,
lightColors =
CardScannerColorScheme
.lightDefaults()
.copy(
frameColor = "#FFD32F2F",
activeControlBackgroundColor = "#FFD32F2F",
activeControlContentColor = "#FFFFFFFF"
),
scanStabilizationMs = 400L,
minCardNumberDetections = 2,
requireExpiryDate = false
)val config =
CardScannerConfig(
themeMode =
CardScannerThemeMode.DARK,
language =
CardScannerLanguage.ARABIC,
darkColors =
CardScannerColorScheme
.darkDefaults()
.copy(
frameColor = "#FFFF5252",
activeControlBackgroundColor = "#FFFF5252",
activeControlContentColor = "#FF000000"
),
scanStabilizationMs = 400L,
minCardNumberDetections = 2,
requireExpiryDate = false
)The scanner can automatically follow the device language and appearance:
val config =
CardScannerConfig(
themeMode =
CardScannerThemeMode.SYSTEM,
language =
CardScannerLanguage.SYSTEM,
scanStabilizationMs = 400L,
minCardNumberDetections = 2,
requireExpiryDate = false
)Available theme modes:
CardScannerThemeMode.LIGHT
CardScannerThemeMode.DARK
CardScannerThemeMode.SYSTEM| Value | Description |
|---|---|
LIGHT |
Always use light mode |
DARK |
Always use dark mode |
SYSTEM |
Follow device theme |
Available languages:
CardScannerLanguage.ENGLISH
CardScannerLanguage.ARABIC
CardScannerLanguage.SYSTEM| Value | Description |
|---|---|
ENGLISH |
Force English scanner UI |
ARABIC |
Force Arabic scanner UI |
SYSTEM |
Follow device language |
Light and dark themes can be customized independently.
lightColors =
CardScannerColorScheme
.lightDefaults()
.copy(
frameColor = "#FFD32F2F",
activeControlBackgroundColor =
"#FFD32F2F",
activeControlContentColor =
"#FFFFFFFF"
)darkColors =
CardScannerColorScheme
.darkDefaults()
.copy(
frameColor = "#FFFF5252",
activeControlBackgroundColor =
"#FFFF5252",
activeControlContentColor =
"#FF000000"
)scanStabilizationMs controls how long a valid candidate remains stable before the scanner accepts it.
Example:
scanStabilizationMs = 400LminCardNumberDetections controls how many times the same valid card number must be detected.
Example:
minCardNumberDetections = 2This can help reduce false OCR results.
Set:
requireExpiryDate = truewhen the scanner should not complete until a valid expiry date has also been detected.
Use:
requireExpiryDate = falsewhen expiry date detection is optional.
The scanner returns a CardScannerResult.
Example:
val result: CardScannerResultAvailable values:
result.cardNumber
result.expiryDate
result.cardHolderNameExample:
val cardNumber =
result.cardNumber.orEmpty()
val expiryDate =
result.expiryDate.orEmpty()
val cardHolderName =
result.cardHolderName.orEmpty()The card number is detected using Google ML Kit OCR.
Before a detected card number is accepted, it is validated using the Luhn algorithm.
This helps reject invalid OCR candidates.
Expiry dates must contain an actual slash.
08/29
08 / 29
08/2029
0829
08 29
08-29
08.29
The scanner normalizes valid expiry dates to:
MM/YY
Example:
08/2029
becomes:
08/29
Cardholder name recognition is performed on the lower section of the card.
The scanner expects uppercase cardholder names.
MOHAMED EBRAHEM
JOHN MICHAEL SMITH
Mohamed Ebrahem
john smith
Common card-related labels are ignored when detecting the holder name.
Examples include:
VISA
MASTERCARD
DEBIT
CREDIT
PLATINUM
A demo application can provide separate buttons for testing each scanner configuration.
For example:
Button(
onClick = {
requestScanner(
englishLightConfig()
)
}
) {
Text("English - Light")
}Button(
onClick = {
requestScanner(
englishDarkConfig()
)
}
) {
Text("English - Dark")
}Button(
onClick = {
requestScanner(
arabicLightConfig()
)
}
) {
Text("Arabic - Light")
}Button(
onClick = {
requestScanner(
arabicDarkConfig()
)
}
) {
Text("Arabic - Dark")
}Button(
onClick = {
requestScanner(
systemConfig()
)
}
) {
Text("System Config")
}Payment-card information is sensitive.
Applications using Card Scanner should:
- Never log full card numbers
- Mask PAN values in application logs
- Avoid storing card information unless required
- Protect card information while in memory and storage
- Use secure network communication
- Follow applicable PCI DSS requirements
Card Scanner performs OCR and card-information extraction. It does not provide payment processing or PCI DSS compliance by itself.
| Technology | Purpose |
|---|---|
| CameraX | Camera preview and frame analysis |
| ML Kit | Text recognition |
| Jetpack Compose | Scanner user interface |
| Kotlin Coroutines | Asynchronous processing |
| Hilt | Dependency injection |
| ViewModel | Scanner state management |
| R8 / ProGuard | Release optimization |
| Maven Publish | Library distribution |
Clone the repository:
git clone https://github.com/mohamedebrahem13/CardScanner.gitOpen the project:
cd CardScannerBuild the release library:
./gradlew :cardscanner:assembleReleasePublish to the local Maven test repository:
./gradlew :cardscanner:publishReleasePublicationToLocalTestRepositoryGenerated Maven files are located under:
cardscanner/build/maven-repository/
Group ID: io.github.mohamedebrahem13
Artifact ID: card-scanner
Version: 1.0.0
Dependency:
implementation(
"io.github.mohamedebrahem13:card-scanner:1.0.0"
)This project is licensed under the Apache License 2.0.
See the LICENSE file for more information.

