Search code examples
androidbarcode-scannerandroid-jetpack-composefirebase-mlkitandroid-camerax

Firebase Barcode scanner using Jetpack compose not working


Trying to migrate barcode scanner to Jetpack compose and updating camera and ML Kit dependencies to latest version.

The current shows the camera view correctly, but it is not scanning the barcodes.
The ImageAnalysis analyzer runs only once.

Code

@Composable
fun CameraPreview(
    data: CameraPreviewData,
) {
    val context = LocalContext.current
    val lifecycleOwner = LocalLifecycleOwner.current

    AndroidView(
        modifier = Modifier
            .fillMaxSize(),
        factory = { AndroidViewContext ->
            PreviewView(AndroidViewContext).apply {
                this.scaleType = PreviewView.ScaleType.FILL_CENTER
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
                // Preview is incorrectly scaled in Compose on some devices without this
                implementationMode = PreviewView.ImplementationMode.COMPATIBLE
            }
        },
        update = { previewView ->
            val cameraSelector: CameraSelector = CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                .build()
            val cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
            val cameraProviderFuture: ListenableFuture<ProcessCameraProvider> =
                ProcessCameraProvider.getInstance(context)

            cameraProviderFuture.addListener({
                val preview: Preview = Preview.Builder()
                    .build()
                    .also {
                        // Attach the viewfinder's surface provider to preview use case
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }
                val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
                val barcodeAnalyser = BarcodeAnalyser { barcodes ->
                    barcodes.forEach { barcode ->
                        barcode.rawValue?.let { barcodeValue ->
                            logError("Barcode value detected: ${barcodeValue}.")
                            // Other handling code
                        }
                    }
                }
                val imageAnalysis: ImageAnalysis = ImageAnalysis.Builder()
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .build()
                    .also {
                        it.setAnalyzer(cameraExecutor, barcodeAnalyser)
                    }

                try {
                    cameraProvider.unbindAll()
                    cameraProvider.bindToLifecycle(
                        lifecycleOwner,
                        cameraSelector,
                        preview,
                        imageAnalysis
                    )
                } catch (exception: Exception) {
                    logError("Use case binding failed with exception : $exception")
                }
            }, ContextCompat.getMainExecutor(context))
        },
    )
}

BarcodeAnalyser

class BarcodeAnalyser(
    private val onBarcodesDetected: (barcodes: List<Barcode>) -> Unit,
) : ImageAnalysis.Analyzer {
    private var lastAnalyzedTimestamp = 0L

    override fun analyze(
        imageProxy: ImageProxy,
    ) {
        logError("Inside analyze")
        val currentTimestamp = System.currentTimeMillis()
        if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {

            imageProxy.image?.let { imageToAnalyze ->
                val options = BarcodeScannerOptions.Builder()
                    .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
                    .build()
                val barcodeScanner = BarcodeScanning.getClient(options)
                val imageToProcess =
                    InputImage.fromMediaImage(imageToAnalyze, imageProxy.imageInfo.rotationDegrees)

                barcodeScanner.process(imageToProcess)
                    .addOnSuccessListener { barcodes ->
                        if (barcodes.isNotEmpty()) {
                            logError("Scanned: $barcodes")
                            onBarcodesDetected(barcodes)
                            imageProxy.close()
                        } else {
                            logError("No barcode scanned")
                        }
                    }
                    .addOnFailureListener { exception ->
                        logError("BarcodeAnalyser: Something went wrong with exception: $exception")
                        imageProxy.close()
                    }
            }
            lastAnalyzedTimestamp = currentTimestamp
        }
    }
}

References


Solution

  • Thanks to Adrian's comment.

    It worked after the following changes.

    In BarcodeAnalyser

    1. Removed imageProxy.close() from addOnSuccessListener and addOnFailureListener. Added it to addOnCompleteListener.
    2. Added imageProxy.close() in else condition as well.
    class BarcodeAnalyser(
        private val onBarcodesDetected: (barcodes: List<Barcode>) -> Unit,
    ) : ImageAnalysis.Analyzer {
        private var lastAnalyzedTimestamp = 0L
    
        override fun analyze(
            imageProxy: ImageProxy,
        ) {
            logError("Inside analyze")
            val currentTimestamp = System.currentTimeMillis()
            if (currentTimestamp - lastAnalyzedTimestamp >= TimeUnit.SECONDS.toMillis(1)) {
    
                imageProxy.image?.let { imageToAnalyze ->
                    // ...Same code
    
                    barcodeScanner.process(imageToProcess)
                        .addOnSuccessListener { barcodes ->
                            if (barcodes.isNotEmpty()) {
                                logError("Scanned: $barcodes")
                                onBarcodesDetected(barcodes)
                                // imageProxy.close()
                            } else {
                                logError("No barcode scanned")
                            }
                        }
                        .addOnFailureListener { exception ->
                            logError("BarcodeAnalyser: Something went wrong with exception: $exception")
                            // imageProxy.close()
                        }
                        .addOnCompleteListener {
                            imageProxy.close()
                        }
                }
                lastAnalyzedTimestamp = currentTimestamp
            } else {
                imageProxy.close()
            }
        }
    }