Using Zxing Library with Jetpack compose
Asked Answered
M

4

11

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?

Morrie answered 26/6, 2021 at 4:57 Comment(0)
R
16

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 },
    )
}
Reflector answered 4/7, 2021 at 12:19 Comment(8)
thanks for the answer, its helpful. just wanted to ask if its specific to barcode view or can be used for qr too? @jose-pose-sMorrie
Hi @bharat-kumar! Yep this code works with barcode and qr.Reflector
i used above code, its scanning fine, but even after navigating to new compose screen, its still active, and scanning the qr if camera faces one. how can i avoid that?Morrie
@bharat-kumar You have at least 3 options: 1º Before navigate turn scanFlag = true and prevent to scan in background but camera is ready if you come back. 2º Pause camera with compoundBarcodeView .pause() but to start again you must call compoundBarcodeView .decodeContinuous { this will be complex in functional programming. 3º Option change navigation system, if you don´t need more camera then use navController.navigate("route") -> route{ popUpTo(route){ saveState = true} launchSingleTop = true restoreState = true } to remove this screen and navigat to destination you want.Reflector
Hi @Mehdi.ncb I edit my code, you shoud use scanFlag = false to enable scan again, is a flag to prevent múltiple scanning, of course if you put always to false will scan same code múltiple times, if you wan´t you can scan wait 10 seconds and then put scanFlag = false for example.Reflector
Hey @BharatKumar I would like to ask if you have some qr code generator for jetpack compose. Every tutorial I find is using kotlin thank you very much.Endocardial
Hi @bigQ, i just had used the qr code scanner and not generator.Morrie
@bharat-kumar you can use a disposable effect in order to call resume() and pause() whenever your composable enters or exits the composition respectively. see my answer bellow for more details on thisArch
S
16

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")
}
Seedman answered 12/4, 2022 at 9:50 Comment(0)
A
8

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.

Arch answered 2/2, 2022 at 10:16 Comment(0)
T
2

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 },
    )

}
Tytybald answered 22/7, 2021 at 13:20 Comment(6)
you can check if the app has camera permission or not.Morrie
Yes I have the permission, I will put the code in the editTytybald
put this code :- this.resume() before or after capture.decode() and check if that helps.Morrie
It has solved my problem, but now I encounter another problem, the app is scanning multiple time the barCode when I'm putting the camera in front of the codeTytybald
Hi @Tytybald I edit my code, you shoud use scanFlag = false to enable scan again, is a flag to prevent múltiple scanning, of course if you put always to false will scan same code múltiple times, if you wan´t you can scan wait 10 seconds and then put scanFlag = false for example.Reflector
Okay, thank you, you helped me a lot !!! I'll text you if I have another problem that I can't solveTytybald

© 2022 - 2025 — McMap. All rights reserved.