Created
February 2, 2025 21:50
-
-
Save dgmltn/c5ac4941185d7923a41284c8adaacc13 to your computer and use it in GitHub Desktop.
MLKit + AndroidX Compose Camera QR Code Scanner
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
implementation(libs.bundles.camerax) | |
implementation(libs.kermit) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.os.Looper | |
import androidx.annotation.OptIn | |
import androidx.camera.compose.CameraXViewfinder | |
import androidx.camera.core.CameraSelector | |
import androidx.camera.core.ExperimentalGetImage | |
import androidx.camera.core.ImageAnalysis | |
import androidx.camera.core.ImageAnalysis.COORDINATE_SYSTEM_ORIGINAL | |
import androidx.camera.core.Preview | |
import androidx.camera.core.SurfaceRequest | |
import androidx.camera.lifecycle.ProcessCameraProvider | |
import androidx.camera.lifecycle.awaitInstance | |
import androidx.camera.mlkit.vision.MlKitAnalyzer | |
import androidx.compose.foundation.layout.fillMaxSize | |
import androidx.compose.runtime.Composable | |
import androidx.compose.runtime.getValue | |
import androidx.compose.runtime.mutableStateOf | |
import androidx.compose.runtime.remember | |
import androidx.compose.runtime.rememberCoroutineScope | |
import androidx.compose.runtime.setValue | |
import androidx.compose.ui.Modifier | |
import androidx.compose.ui.platform.LocalContext | |
import androidx.core.content.ContextCompat | |
import androidx.core.util.Consumer | |
import androidx.lifecycle.compose.LifecycleStartEffect | |
import androidx.lifecycle.compose.LocalLifecycleOwner | |
import co.touchlab.kermit.Logger | |
import com.google.mlkit.vision.barcode.BarcodeScannerOptions | |
import com.google.mlkit.vision.barcode.BarcodeScanning | |
import com.google.mlkit.vision.barcode.common.Barcode | |
import kotlinx.coroutines.launch | |
import java.util.concurrent.Executors | |
@OptIn(ExperimentalGetImage::class) | |
@Composable | |
fun CameraXandMlKitQrCodeScanner( | |
onScanned: (String) -> Unit, | |
modifier: Modifier = Modifier, | |
) { | |
val scope = rememberCoroutineScope() | |
val context = LocalContext.current | |
var currentSurfaceRequest: SurfaceRequest? by remember { mutableStateOf(null) } | |
val lifecycleOwner = LocalLifecycleOwner.current | |
// CameraX Lifecycle-aware controller | |
val analysisExecutor = remember { Executors.newSingleThreadExecutor() } | |
val mainThreadExecutor = remember { ContextCompat.getMainExecutor(context) } | |
// Select back-facing camera | |
val cameraSelector = remember { | |
CameraSelector.Builder() | |
.requireLensFacing(CameraSelector.LENS_FACING_BACK) | |
.build() | |
} | |
val barcodeScanner = remember { | |
val options = BarcodeScannerOptions.Builder() | |
.setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS) | |
.enableAllPotentialBarcodes() | |
.build() | |
BarcodeScanning.getClient(options) | |
} | |
val mlKitConsumer = object: Consumer<MlKitAnalyzer.Result> { | |
override fun accept(result: MlKitAnalyzer.Result) { | |
check(Looper.myLooper() == Looper.getMainLooper()) { "must be called on the main thread" } | |
val barcodeResults: List<Barcode>? = result.getValue(barcodeScanner) | |
barcodeResults?.let { | |
it.mapNotNull { it.rawValue }.filter { it.isNotEmpty() }.forEach { barcode -> | |
onScanned(barcode) | |
Logger.i { "Found Barcode: $barcode" } | |
} | |
} | |
} | |
} | |
val previewUseCase = remember { | |
Preview.Builder() | |
.build() | |
.apply { | |
setSurfaceProvider { | |
currentSurfaceRequest = it | |
} | |
} | |
} | |
val qrCodeAnalyzerUseCase = remember { | |
ImageAnalysis | |
.Builder() | |
.build() | |
.apply { | |
setAnalyzer( | |
analysisExecutor, | |
MlKitAnalyzer( | |
listOf(barcodeScanner), | |
COORDINATE_SYSTEM_ORIGINAL, | |
mainThreadExecutor, | |
mlKitConsumer | |
) | |
) | |
} | |
} | |
LifecycleStartEffect(Unit) { | |
var cameraProvider: ProcessCameraProvider? = null | |
scope.launch { | |
cameraProvider = ProcessCameraProvider.awaitInstance(context) | |
Logger.d { "cameraProvider $cameraProvider" } | |
cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, previewUseCase, qrCodeAnalyzerUseCase) | |
} | |
onStopOrDispose { | |
cameraProvider?.unbindAll() | |
} | |
} | |
currentSurfaceRequest?.let { surfaceRequest -> | |
CameraXViewfinder( | |
surfaceRequest = surfaceRequest, | |
modifier = modifier | |
.fillMaxSize() | |
) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[versions] | |
# https://github.com/touchlab/Kermit/releases | |
kermit = "2.0.2" | |
# https://developer.android.com/jetpack/androidx/releases/camera | |
# https://developer.android.com/reference/kotlin/androidx/camera/compose/package-summary | |
camerax = "1.5.0-alpha05" | |
[libraries] | |
kermit = { group = "co.touchlab", name = "kermit", version.ref = "kermit" } | |
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" } | |
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" } | |
androidx-camera-mlkit-vision = { module = "androidx.camera:camera-mlkit-vision", version.ref = "camerax" } | |
androidx-camera-compose = { module = "androidx.camera:camera-compose", version.ref = "camerax" } | |
[bundles] | |
camerax = [ "androidx-camera-camera2", "androidx-camera-lifecycle", "androidx-camera-mlkit-vision", "androidx-camera-compose" ] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment