Scanbot SDK has been acquired by Apryse! Learn more

Learn more
Skip to content

Building an Android MICR reader app in Kotlin

Kevin August 22, 2025 9 mins read
Android Data Capture SDK

In this tutorial, we’ll use Android Studio and the Scanbot Check Scanner SDK to create an app for extracting the routing number, account number, and check number from the MICR (magnetic ink character recognition) line on paper checks.

Scanning the MICR line on a check with our Android MICR reader app

We’ll achieve this by following these steps:

  1. Preparing the project
  2. Initializing the SDK
  3. Setting up the main screen
  4. Implementing the MICR scanning feature
  5. Setting the expected check formats

All you need is the latest version of Android Studio and you’re good to go.

Want to see the final code right away? Click here.

MainActivity.kt:

package com.example.androidmicrreader

import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import android.widget.Button
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog
import io.scanbot.sdk.check.entity.*
import io.scanbot.sdk.ui_v2.check.configuration.CheckScannerScreenConfiguration
import io.scanbot.sdk.ui_v2.check.CheckScannerActivity

class MainActivity : AppCompatActivity() {
    val resultLauncher: ActivityResultLauncher<CheckScannerScreenConfiguration> =
        registerForActivityResult(CheckScannerActivity.ResultContract()) { resultEntity: CheckScannerActivity.Result ->
            if (resultEntity.resultOk) {
                resultEntity.result?.check?.let { genericDocument ->
                    when (genericDocument.type.name) {
                        INDCheck.DOCUMENT_TYPE -> {
                            val check = INDCheck(genericDocument)
                            val routingNumber = check.sortNumber?.value?.text ?: "N/A"
                            val accountNumber = check.accountNumber?.value?.text ?: "N/A"
                            val checkNumber = check.serialNumber?.value?.text ?: "N/A"

                            AlertDialog.Builder(this)
                                .setTitle("Check scanned")
                                .setMessage("Routing number: $routingNumber\nAccount number: $accountNumber\nCheck number: $checkNumber")
                                .setPositiveButton("OK") { dialog, _ ->
                                    dialog.dismiss()
                                }
                                .show()
                        }
                        else -> {
                            AlertDialog.Builder(this)
                                .setTitle("Check scanned")
                                .setMessage("Unknown check format")
                                .setPositiveButton("OK") { dialog, _ ->
                                    dialog.dismiss()
                                }
                                .show()
                        }
                    }
                }
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        findViewById<Button>(R.id.btn_scan_micr).setOnClickListener {
            val configuration = CheckScannerScreenConfiguration()
            resultLauncher.launch(configuration)
        }
    }
}

Step 1: Prepare the project

Create a new Empty Views Activity and name the project (e.g., “Android MICR Reader”).

When your project is ready, go to settings.gradle.kts and add the Maven repositories for our SDK:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()

        // Add the repositories here:
        maven(url = "https://nexus.scanbot.io/nexus/content/repositories/releases/")
        maven(url = "https://nexus.scanbot.io/nexus/content/repositories/snapshots/")
    }
}

Now go to app/build.gradle.kts and add the dependencies for the Scanbot SDK and the RTU UI:

dependencies {
    implementation("io.scanbot:sdk-package-4:7.1.0")
    implementation("io.scanbot:sdk-check-assets:7.1.0")
    implementation("io.scanbot:rtu-ui-v2-bundle:7.1.0")

Sync the project.

💡 We use Scanbot SDK version 7.1.0 in this tutorial. You can find the latest version in the changelog.

We need to access the device camera to scan MICR codes. Therefore, add the necessary permissions in AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    // Add the permissions here:
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />

    <application
    // ...

Step 2: Initialize the SDK

Before we can use the Scanbot Check Scanner SDK, we need to initialize it. The recommended approach is to do it in your Application implementation. This ensures the SDK is correctly initialized even when the app‘s process is restored after being terminated in the background.

First, we need to create an Application subclass by right-clicking on the folder app/kotlin+java/com.example.androidmicrreader, selecting New > Kotlin Class/File, and naming it (e.g., “ExampleApplication”).

In the resulting ExampleApplication.kt, let’s first add the necessary imports.

import android.app.Application
import io.scanbot.sdk.ScanbotSDKInitializer

Then we make ExampleApplication extend the Application class by adding : Application() and putting the code for initializing the SDK inside it.

class ExampleApplication : Application() {

    override fun onCreate() {
        super.onCreate()

        ScanbotSDKInitializer()
            // Optional: Uncomment the next line if you have a license key.
            // .license(this, LICENSE_KEY)
            .initialize(this)
    }
}

💡 Without a license key, our SDK only runs for 60 seconds per session. This is more than enough for the purposes of our tutorial, but if you like, you can generate a license key. Just make sure to change your applicationId in app/build.gradle.kts to io.scanbot.androiddocumentscanner and use that ID for generating the license.

Finally, we need to register the ExampleApplication class in AndroidManifest.xml.

<application
   android:name=".ExampleApplication"
   // ...

Step 3: Set up the main screen

We’re going to build a rudimentary UI so we can quickly start the MICR reader from the main screen.

For this tutorial, we’re going to go with a single-button layout, which you can copy and paste into app/res/layout/activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_scan_micr"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp"
        android:text="Scan MICR"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

Next, we’ll connect the button to our RTU UI’s scanning screen.

Step 4: Implement the MICR scanning feature

Go to MainActivity.kt and add the necessary imports.

import android.widget.Button
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog
import io.scanbot.sdk.check.entity.*
import io.scanbot.sdk.ui_v2.check.configuration.CheckScannerScreenConfiguration
import io.scanbot.sdk.ui_v2.check.CheckScannerActivity

In the MainActivity class, add a property that we’ll use to launch the MICR reader.

class MainActivity : AppCompatActivity() {
    val resultLauncher: ActivityResultLauncher<CheckScannerScreenConfiguration> =
        registerForActivityResult(CheckScannerActivity.ResultContract()) { resultEntity: CheckScannerActivity.Result ->
            if (resultEntity.resultOk) {
                resultEntity.result?.check?.let { genericDocument ->
                    // Code from step 5 will go here.
                }
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
    // ...

In the onCreate() method, set up an OnClickListener for the Scan MICR button. When the button is clicked, it starts the resultLauncher using a configuration object.

// ... paste after the existing code in onCreate():
findViewById<Button>(R.id.btn_scan_micr).setOnClickListener {
    val configuration = CheckScannerScreenConfiguration()
    
    // Start the recognizer activity.
    resultLauncher.launch(configuration)
}

Step 5: Set the expected check formats

Now we’ll need to process the scan result. In this example, we’ll look for the Indian check format (INDCheck). For the full list of supported check formats, please refer to the SDK’s API documentation.

Once we have the check information, we can extract the specific data we need. In this example, we’ll extract the routing number, account number, and check number and display them to the user in an AlertDialog.

val resultLauncher: ActivityResultLauncher<CheckScannerScreenConfiguration> =
    registerForActivityResult(CheckScannerActivity.ResultContract()) { resultEntity: CheckScannerActivity.Result ->
        if (resultEntity.resultOk) {
            resultEntity.result?.check?.let { genericDocument ->
                when (genericDocument.type.name) {
                    INDCheck.DOCUMENT_TYPE -> {
                        val check = INDCheck(genericDocument)
                        val routingNumber = check.sortNumber?.value?.text ?: "N/A"
                        val accountNumber = check.accountNumber?.value?.text ?: "N/A"
                        val checkNumber = check.serialNumber?.value?.text ?: "N/A"

                        AlertDialog.Builder(this)
                            .setTitle("Check scanned")
                            .setMessage("Routing number: $routingNumber\nAccount number: $accountNumber\nCheck number: $checkNumber")
                            .setPositiveButton("OK") { dialog, _ ->
                                dialog.dismiss()
                            }
                            .show()
                    }
                    else -> {
                        AlertDialog.Builder(this)
                            .setTitle("Check scanned")
                            .setMessage("Unknown check format")
                            .setPositiveButton("OK") { dialog, _ ->
                                dialog.dismiss()
                            }
                            .show()
                    }
                }
            }
        }
    }

Your final MainActivity.kt will look like this:

package com.example.androidmicrreader

import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import android.widget.Button
import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog
import io.scanbot.sdk.check.entity.*
import io.scanbot.sdk.ui_v2.check.configuration.CheckScannerScreenConfiguration
import io.scanbot.sdk.ui_v2.check.CheckScannerActivity

class MainActivity : AppCompatActivity() {
    val resultLauncher: ActivityResultLauncher<CheckScannerScreenConfiguration> =
        registerForActivityResult(CheckScannerActivity.ResultContract()) { resultEntity: CheckScannerActivity.Result ->
            if (resultEntity.resultOk) {
                resultEntity.result?.check?.let { genericDocument ->
                    when (genericDocument.type.name) {
                        INDCheck.DOCUMENT_TYPE -> {
                            val check = INDCheck(genericDocument)
                            val routingNumber = check.sortNumber?.value?.text ?: "N/A"
                            val accountNumber = check.accountNumber?.value?.text ?: "N/A"
                            val checkNumber = check.serialNumber?.value?.text ?: "N/A"

                            AlertDialog.Builder(this)
                                .setTitle("Check scanned")
                                .setMessage("Routing number: $routingNumber\nAccount number: $accountNumber\nCheck number: $checkNumber")
                                .setPositiveButton("OK") { dialog, _ ->
                                    dialog.dismiss()
                                }
                                .show()
                        }
                        else -> {
                            AlertDialog.Builder(this)
                                .setTitle("Check scanned")
                                .setMessage("Unknown check format")
                                .setPositiveButton("OK") { dialog, _ ->
                                    dialog.dismiss()
                                }
                                .show()
                        }
                    }
                }
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        findViewById<Button>(R.id.btn_scan_micr).setOnClickListener {
            val configuration = CheckScannerScreenConfiguration()
            resultLauncher.launch(configuration)
        }
    }
}

Now build and run the app and scan a check …

Example of a paper check with an MICR code at the bottom

… to test your scanner!

Scanning the MICR line on a check with our Android MICR reader app

Conclusion

Congratulations! You’ve successfully integrated powerful MICR scanning functionalities into your Android app 🎉

You can further customize the scanning interface to suit your preferences. Head over to the SDK’s RTU UI documentation to learn more.

If this tutorial has piqued your interest in integrating data capture functionalities into your Android app, make sure to take a look at the other neat features in the Data Capture SDK’s documentation – or run our example project for a more hands-on experience.

Should you have questions about this tutorial or run into any issues, we’re happy to help! Just shoot us an email via tutorial-support@scanbot.io.

Happy scanning! 🤳

Related blog posts

Experience our demo apps

Barcode Icon Art

Barcode Scanner SDK

Scan 1D and 2D barcodes reliably in under 0.04s. Try features like Batch Scanning, Scan & Count, and our AR Overlays.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Web QR Code

Also available to download from:

Document Icon Art

Document Scanner SDK

Scan documents quickly and accurately with our free demo app. Create crisp digital scans in seconds.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Black and white QR code. Scan this code for quick access to information.

Also available to download from:

Data_capture Icon Art

Data Capture Modules

Try fast, accurate data capture with our demo app. Extract data from any document instantly – 100% secure.

Launch Web Demo

Scan the code to launch the web demo on your phone.

Black and white QR code. Scan this quick response code with your smartphone.

Also available to download from: