Android 🤖

Our CardScanActivity Kotlin Activity makes it easy to add insurance card scanning to any Android application in 5 minutes or less.

Requirements

  • Android API level 23 or higher

  • AndroidX compatibility

  • Kotlin coroutine compatibility

Installation

The library is published in the maven central repository, so installation is as simple as adding the dependency to your build.gradle

dependencies {
    implementation 'ai.cardscan:insurance-cardscan:0.3.5'   
}

Usage

Import the necessary classes from the library into your activity or fragment:

import ai.cardscan.insurance.CardScanActivity
// import ai.cardscan.insurance.data.*
import ai.cardscan.insurance.data.CardScanHelper
import ai.cardscan.insurance.data.CardScanConfig

Basic usage Example:

import ai.cardscan.insurance.CardScanActivity
import ai.cardscan.insurance.data.CardScanConfig
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity

class OnboardingActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<View>(R.id.btnScanCard).setOnClickListener {
            setUpCardScanHelper()
            launchCardScanActivity()
        }
    }

    private fun launchCardScanActivity() {
        CardScanActivity.launch(
            // Pass an activity to the launcher
            this@MainActivity,
            // A CardScanConfig instance to connect to the server and customize UI
            CardScanConfig(
                // your token goes here
                sessionToken = "xxx",
            ),
            // And other optional parameters to alter behavior
            closeAfterSuccess = true, // Close the scanner after successful scan? 
                                    // defaults to true
            closeAfterSuccessDelay = 1500 // If set to close automatically, 
                                // how long to wait after scan to close? In milliseconds
            closeAfterErrorDelay = 2000 // Cardscan activity is killed automatically after an error
                                // You can decide between 0 and 30 seconds, how long to wait before closing                    
        )
    }
    
    
    private fun setUpCardScanHelper() {
        // Helper class to setup observers and create the appropiate callbacks
        // CardScanHelper takes in a lifecycleOwner, 
        // "this" in the case of setup within an activity,
        // or the host activity in the case of a fragment, or viewLifecycleOwner
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                Toast.makeText(this@MainActivity, "Card Scanned: ${card.cardId}", Toast.LENGTH_SHORT).show()
            }
            onError { error: CardError? ->
                Toast.makeText(this@MainActivity, "Error encountered!", Toast.LENGTH_SHORT).show()
                print(error?.message)
            }
            onRetry {
                Toast.makeText(this@MainActivity, "User retried after failed scan!", Toast.LENGTH_SHORT).show()
            }
            onCancel {
                Toast.makeText(this@MainActivity, "User closed the scanner with close button!", Toast.LENGTH_SHORT).show()
            }
            onEligibilitySuccess { eligibility:Eligibility ->
                Toast.makeText(this@MainActivity, "Eligibility info found!", Toast.LENGTH_SHORT).show()
            }
            onEligibilityError { eligibilityError:EligibilityError? ->
                Toast.makeText(this@MainActivity, "Error during eligiblity check!", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

Available Properties

When launchingCardScanActivity, a CardScanConfig instance should be passed with properties for server connection and UI customization.

CardScanConfig(
  // Required
  sessionToken: token,
  live: false,

  // Optional
  backsideSupport: scanBackside,
  eligibility = EligibilityRequest( // We now support eligibility checks!
                    Subscriber(
                        firstName = "Subscriber's first name",
                        lastName = "Subscriber's last name",
                        dateOfBirth = "19020403" // Always in format YYYYMMDD
                    ),
                    Provider(
                        firstName = "Provider's first name",
                        lastName = "Provider's last name",
                        npi = "12345677"
                    )
                )

  // UI Customization
  messages = CardScanMessages(
                    autoCaptureTitle = "My Auto Capture Title",
                    manualCaptureTitle = "My Manual Capture Title",
                    processingTitle = "My Processing Title",
                    frontSideCompletedTitle = "My FrontSide Completed Title",
                    completedTitle = "My Completed Title",
                    retryTitle = "Me Retry Title",
                    errorTitle = "My Error Title",
                    cameraErrorTitle = "My Camera Error Title"
                )
  messageFontSize: messageFontSize, // Float
  messageTextColor: messageTextColor, // Int
  autoSwitchActiveColor: autoSwitchActiveColor, // Int
  autoSwitchInactiveColor: autoSwitchInactiveColor, // Int
  progressBarColor: progressBarColor, // Int
  widgetBackgroundColor: widgetBackgroundColor, // Int
)

Main Config Props

UI/UX Customization Props

The card scanner view is designed to be customizable. Please see the Customization ⚙️ section of UI Components to adjust these elements to match your application's branding and theme:

Note: All UI/UX Props are optional

Cardscan activity launcher props

onSuccess Callback

onSuccess triggers when card scanning process completes successfully. This function receives the scanned card data as an argument.

Usage

To use the onSuccess callback, pass a function that receives the scanned card data to the onSuccess function in CardScanHelper:

fun handleCardScanSuccess(card: ScannedCard?) {
  print("Card scanned successfully: $card")
}

 private fun setUpCardScanHelper() {
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                handleCardScanSuccess(card)
            }
        }
    }
)

In this example, the handleCardScanSuccess function logs the scanned card data to the console when the scanning process is completed successfully and your MainActivity or fragment resumes.

onError Callback

onError triggers after a failure occurs during the card scanning process. This function receives an error object as an argument.

Usage

Pass a function that receives the error object or implement your callback directly inside the helper:

fun handleCardScanError(card: CardError?) {
  print("Scanning failed: $card")
}

 private fun setUpCardScanHelper() {
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                handleCardScanSuccess(card)
            }
            onError { error: CardError? -> 
                handleCardScanError(error)
            }
        }
    }
)

In this example, the handleCardScanError logs the error object when a scanning failure occurs.

By using the onSuccess and onError callbacks, you can handle successful and failed scanning event with custom actions or display appropriate messages to the user.

onCancel and onRetry Callbacks

onCancel expects a function that will trigger after users cancel the card scanning process. This could be useful for tracking user behavior or displaying an appropriate message. onRetry expects a function that will trigger when your activity or fragment resumes, if the user had a retry of a scanning process.

private fun setUpCardScanHelper() {
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                handleCardScanSuccess(card)
            }
            onError { error: CardError? -> 
                handleCardScanError(error)
            }
            onCancel {
                // Handle a user closing the scanner with the close button!
            }
            onRetry {
                // Triggered after the user retried a scan
            }
        }
    }

onEligibilitySuccess and onEligibilityError Callbacks

Both callbacks expect a function that will trigger in either case. onElibiligitySuccess callback will pass an Eligibility object and onEligibilityError will pas an EligibilityError object. Expect the same behavior as the other callbacks, this objects will be passed once your activity or fragment resumes.

private fun setUpCardScanHelper() {
        cardScanHelper = CardScanHelper(this).apply {
            onSuccess { card: ScannedCard ->
                handleCardScanSuccess(card)
            }
            onError { error: CardError? -> 
                handleCardScanError(error)
            }
            onCancel {
                // Handle a user closing the scanner with the close button!
            }
            onRetry {
                // Triggered after the user retried a scan
            }
            onEligibilitySuccess { eligibility:Eligibility ->
                handleEligibilitySuccess(eligibility)
            }
            onEligibilityError { eligibilityError:EligibilityError? ->
                handleEligibilityError(eligibilityError)
            }
        }
    }

Camera Permissions 📸

Our SDK requires camera access to scan insurance cards. While the CardScan widget automatically requests camera permissions during widget load, it does not present any UI for handling permissions or manage permission failures, particularly on mobile devices.

Requirement: We require that developers handle camera permission requests before loading the CardScan widget. This will allow you to manage the permission flow consistently within your application, providing custom error handling and user feedback if permission is denied.

Recommendation: We recommend following best practices when requesting camera permissions:

  • Pre-flight the request:

    Within your app, prompt the user with a clear explanation of why camera access is needed and ask for confirmation (Yes/No). This improves transparency, builds trust, and can reduce the likelihood of the user denying the request.

  • Handle permission rejection gracefully:

    • Soft rejection (user-level): If the user declines the camera access within your app, provide a helpful message that explains the impact of this choice and give them the option to reconsider later.

    • Hard rejection (system-level): In case the user has previously denied the camera permission at the system level, guide them to the device settings where they can manually enable the camera access. Display an appropriate message explaining how to do this and the importance of enabling the permission for the app's functionality.

    For Android applications, we recommend taking a dive into the official docs: https://developer.android.com/training/permissions/requesting

Suggestion for code to include with Cardscan launcher:

// Permission launcher, displays dialog asking for permission
val requestPermissionLauncher =
    registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted: Boolean ->
        if (isGranted) {
            // Permission is granted, you can launch Cardscan activity safely
            launchCardScanActivity()
        } else {
            // Permission denied or dismissed.
            // Explain to user that feature is unavailable, because permission
            // is needed to use it.
        }
    }   
// Check permission status and act on it 
     private fun handleScannerOpen() {
        when {
            ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.CAMERA
            ) == PackageManager.PERMISSION_GRANTED -> {
                // Permission is granted, you can start
                // Cardscan activity safely.
                launchCardScanActivity()
            }

            ActivityCompat.shouldShowRequestPermissionRationale(
                this,
                Manifest.permission.CAMERA
            ) -> {
                // Show UI to app user to tell them why you need the permission.
                // This is a good place to take them to settings too
                // so they can grant permission there.
            }

            else -> {
                // Here you can directly ask for the permission
                requestPermissionLauncher.launch(Manifest.permission.CAMERA)
            }
        }
    }

Last updated