I'm currently experimenting with CameraX API and ML Kit for barcode scanning. I'm not receiving any data from PreviewView when I'm pointing camera to the QR code. Let me share with you my code, I think I'm so close, but sadly couldn't figure it out. :/ I'm using Preview and Image Analysis cases
MainActivity with ImageAnalysis.Analyser
class MainActivity : AppCompatActivity() {
private lateinit var outputDirectory: File
private lateinit var cameraExecutor: ExecutorService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
outputDirectory = getOutputDirectory()
cameraExecutor = Executors.newSingleThreadExecutor()
// Request camera permissions
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(
this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
)
}
}
private fun getOutputDirectory(): File {
val mediaDir = externalMediaDirs.firstOrNull()?.let {
File(it, resources.getString(R.string.app_name)).apply { mkdirs() }
}
return if (mediaDir != null && mediaDir.exists())
mediaDir else filesDir
}
override fun onRequestPermissionsResult(
requestCode: Int, permissions: Array<String>, grantResults:
IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(
this,
"Permissions not granted by the user.",
Toast.LENGTH_SHORT
).show()
finish()
}
}
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(
baseContext, it
) == PackageManager.PERMISSION_GRANTED
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
// Preview
val preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(previewView.surfaceProvider)
}
// Image Analyzer
val imageAnalyzer = ImageAnalysis.Builder()
.build()
.also {
it.setAnalyzer(cameraExecutor, MyImageAnalyser())
}
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
this, cameraSelector, preview, imageAnalyzer
)
} catch (exc: Exception) {
Log.e(Constants.TAG, "Use case binding failed", exc)
}
}, ContextCompat.getMainExecutor(this))
}
inner class MyImageAnalyser : ImageAnalysis.Analyzer {
@SuppressLint("UnsafeExperimentalUsageError")
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image =
InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
initializeBarcodeScanner(image)
}
}
private fun initializeBarcodeScanner(inputImage: InputImage) {
val options = BarcodeScannerOptions.Builder()
.setBarcodeFormats(
Barcode.QR_CODE,
Barcode.WIFI,
Barcode.URL
)
.build()
val scanner = BarcodeScanning.getClient(options)
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
for (barcode in barcodes) {
val bounds = barcode.boundingBox
val corners = barcode.cornerPoints
val rawValue = barcode.rawValue
Log.d("BARCODE", barcode.valueType.toString())
when (barcode.valueType) {
Barcode.WIFI -> {
val ssid = barcode.wifi!!.ssid
val password = barcode.wifi!!.password
val type = barcode.wifi!!.encryptionType
Log.d("BARCODE", ssid.toString())
Log.d("BARCODE", password.toString())
Log.d("BARCODE", type.toString())
}
Barcode.URL -> {
val title = barcode.url!!.title
val url = barcode.url!!.url
Log.d("BARCODE", title.toString())
Log.d("BARCODE", url.toString())
}
Barcode.QR_CODE -> {
Log.d("BARCODE", barcode.rawValue.toString())
}
else -> {
Log.d("BARCODE", "Else")
}
}
}
}
.addOnFailureListener {
Log.d("BARCODE", it.message.toString())
}
}
}
override fun onDestroy() {
super.onDestroy()
cameraExecutor.shutdown()
}
}
Constants.kt
class Constants {
companion object {
const val TAG = "CameraXBasic"
const val REQUEST_CODE_PERMISSIONS = 10
val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
}
}
activity_main.xml
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
</androidx.camera.view.PreviewView>
<TextView
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="200dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:text="TextView"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Dependencies
// Use this dependency to bundle the model with your app
implementation 'com.google.mlkit:barcode-scanning:16.0.3'
def camerax_version = "1.0.0-beta10"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha17"
Every image the analyzer receives must be closed before it can receive new images. The documentation says:
Closing an image is achieved via
ImageProxy.close()
. Since you're using MLKit, you can add a task completion listener, and close the image inside it. The listener is called when the task (image processing) is completed successfully or with an error.