Using Zxing Library with Jetpack compose

Viewed 3860

I am trying to implement qr scanner using zxing library. For this, i have added a button on screen, and on click of it, i am launching scanner as below

Button(
        onClick = {
            val intentIntegrator = IntentIntegrator(context)
            intentIntegrator.setPrompt(QrScanLabel)
            intentIntegrator.setOrientationLocked(true)
            intentIntegrator.initiateScan()
        },
        modifier = Modifier
            .fillMaxWidth()
    ) {
        Text(
            text = QrScanLabel
        )
    }

but, it launches an intent, which expects onActivityResult method to get back the results. And Jetpack compose uses rememberLauncherForActivityResult like below

val intentLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartIntentSenderForResult()
    ) {
        if (it.resultCode != RESULT_OK) {
            return@rememberLauncherForActivityResult
        }
        ...
    }

but how do we integrate both things together here?

4 Answers

I make a provisional solution with same library: Gradle dependencies:

implementation('com.journeyapps:zxing-android-embedded:4.1.0') { transitive = false }
implementation 'com.google.zxing:core:3.4.0'

My new Screen with jetpack compose and camera capture, that works for my app:

@Composable
fun AdminClubMembershipScanScreen(navController: NavHostController) {
    val context = LocalContext.current
    var scanFlag by remember {
        mutableStateOf(false)
    }

    val compoundBarcodeView = remember {
        CompoundBarcodeView(context).apply {
            val capture = CaptureManager(context as Activity, this)
            capture.initializeFromIntent(context.intent, null)
            this.setStatusText("")
            capture.decode()
            this.decodeContinuous { result ->
                if(scanFlag){
                    return@decodeContinuous
                }
                scanFlag = true
                result.text?.let { barCodeOrQr->
                    //Do something and when you finish this something
                    //put scanFlag = false to scan another item
                    scanFlag = false
                }
                //If you don't put this scanFlag = false, it will never work again.
                //you can put a delay over 2 seconds and then scanFlag = false to prevent multiple scanning 
                
            }
        }
    }

    AndroidView(
        modifier = Modifier,
        factory = { compoundBarcodeView },
    )
}

Since zxing-android-embedded:4.3.0 there is a ScanContract, which can be used directly from Compose:

val scanLauncher = rememberLauncherForActivityResult(
    contract = ScanContract(),
    onResult = { result -> Log.i(TAG, "scanned code: ${result.contents}") }
)

Button(onClick = { scanLauncher.launch(ScanOptions()) }) {
    Text(text = "Scan barcode")
}

Addendum to the accepted answer

This answer dives into the issues commented on by @Bharat Kumar and @Jose Pose S in the accepted answer.

I basically just implemented the accepted answer in my code and then added the following code just after the defining compundBarCodeView

DisposableEffect(key1 = "someKey" ){
    compoundBarcodeView.resume()
    onDispose {
        compoundBarcodeView.pause()
    }
}

this makes sure the scanner is only active while it is in the foreground and unbourdens our device.

TL;DR

In escence even after you scan a QR code successfully and leave the scanner screen, the barcodeview will "haunt" you by continuing to scan from the backstack. which you usually dont want. And even if you use a boolean flag to prevent the scanner from doing anything after the focus has switched away from the scanner it will still burden your processor and slow down your UI since there is still a process constantly decrypting hi-res images in the background.

I have a problem, I've the same code as you, but i don't know why it's showing me a black screen

Code AddProduct

@ExperimentalPermissionsApi
@Composable
fun AddProduct(
    navController: NavController
) {
    val context = LocalContext.current
    var scanFlag by remember {
        mutableStateOf(false)
    }

    val compoundBarcodeView = remember {
        CompoundBarcodeView(context).apply {
            val capture = CaptureManager(context as Activity, this)
            capture.initializeFromIntent(context.intent, null)
            this.setStatusText("")
            capture.decode()
            this.decodeContinuous { result ->
                if(scanFlag){
                    return@decodeContinuous
                }
                scanFlag = true
                result.text?.let { barCodeOrQr->
                    //Do something

                }
                scanFlag = false
            }
        }
    }

    AndroidView(
        modifier = Modifier.fillMaxSize(),
        factory = { compoundBarcodeView },
    )

}
Related