skip to Main Content

I am trying to integrate CameraX inside my flutter project. I am doing so via platform channels. I don’t want to integrate any third party lib.

Here is a screenshot of the camera only occupying one third of the height in Android

enter image description here

Below is my code

class ScanQr extends StatelessWidget {
  const ScanQr({super.key});

  @override
  Widget build(BuildContext context) {
    var width = MediaQuery.of(context).size.width;
    var height = MediaQuery.of(context).size.height;

    return Scaffold(
      backgroundColor: Colors.teal,
      body: SizedBox(
        height: height, 
        width: width,
        child: CealScanQrView(
          width: width,
          height: height,
        ),
      ),
    );
  }
}


class CealScanQrView extends StatelessWidget {
  const CealScanQrView({required this.width, required this.height, super.key});

  final double width;
  final double height;
 
  @override
  Widget build(BuildContext context) {
    final Map<String, dynamic> creationParams = <String, dynamic>{};

    creationParams["width"] = width;
    creationParams["height"] = height;

    return Platform.isAndroid
        ? AndroidView(
            viewType: cealScanQrView,
            layoutDirection: TextDirection.ltr,
            creationParams: creationParams,
            creationParamsCodec: const StandardMessageCodec(),
          )
        : UiKitView(
            viewType: cealScanQrView,
            layoutDirection: TextDirection.ltr,
            creationParams: creationParams,
            creationParamsCodec: const StandardMessageCodec(),
          );
  }
}

Here is my android code

In MainActivity's configureFlutterEngine method

flutterEngine
            .platformViewsController
            .registry
            .registerViewFactory("cealScanQrView", CealScanQrViewFactory(this))


class CealScanQrView(
    private val context: Context, id: Int, creationParams: Map<String?, Any?>?,
    private val activity: FlutterActivity
) : PlatformView {

    private var mCameraProvider: ProcessCameraProvider? = null
    private var linearLayout: LinearLayout = LinearLayout(context)

    private var preview: PreviewView = PreviewView(context)

    private lateinit var cameraExecutor: ExecutorService
    private lateinit var options: BarcodeScannerOptions
    private lateinit var scanner: BarcodeScanner

    private var analysisUseCase: ImageAnalysis = ImageAnalysis.Builder()
        .build()

    companion object {
        private val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS = mutableListOf(Manifest.permission.CAMERA).toTypedArray()
    }


    init {
        val linearLayoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )

        linearLayout.layoutParams = linearLayoutParams
        linearLayout.orientation = LinearLayout.VERTICAL

        preview.setBackgroundColor(Color.RED)
        preview.layoutParams = ViewGroup.LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )

        linearLayout.addView(preview)
        linearLayout.layoutParams.width = 400
        linearLayout.layoutParams.height = 400
        setUpCamera()

        preview.viewTreeObserver.addOnGlobalLayoutListener(object :
            ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                preview.viewTreeObserver.removeOnGlobalLayoutListener(this)
                preview.layoutParams.height =
                    creationParams?.get("height").toString().toDouble().toInt()
                preview.requestLayout()
            }
        })
    }

    private fun setUpCamera() {
        if (allPermissionsGranted()) {
            startCamera()
        } else {
            ActivityCompat.requestPermissions(
                context as FlutterActivity, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS
            )
        }
        cameraExecutor = Executors.newSingleThreadExecutor()

        options = BarcodeScannerOptions.Builder()
            .setBarcodeFormats(
                Barcode.FORMAT_QR_CODE
            )
            .build()
        scanner = BarcodeScanning.getClient(options)
        analysisUseCase.setAnalyzer(
            // newSingleThreadExecutor() will let us perform analysis on a single worker thread
            Executors.newSingleThreadExecutor()
        ) { imageProxy ->
            processImageProxy(scanner, imageProxy)
        }
    }

    override fun getView(): View {
        return linearLayout
    }

    override fun dispose() {
        cameraExecutor.shutdown()
    }

    @SuppressLint("UnsafeOptInUsageError")
    private fun processImageProxy(
        barcodeScanner: BarcodeScanner,
        imageProxy: ImageProxy
    ) {
        imageProxy.image?.let { image ->
            val inputImage =
                InputImage.fromMediaImage(
                    image,
                    imageProxy.imageInfo.rotationDegrees
                )
            barcodeScanner.process(inputImage)
                .addOnSuccessListener { barcodeList ->
                    val barcode = barcodeList.getOrNull(0)
                    // `rawValue` is the decoded value of the barcode
                    barcode?.rawValue?.let { value ->
                        mCameraProvider?.unbindAll()
                        Toast.makeText(context, value, Toast.LENGTH_LONG).show()
                    }
                }
                .addOnFailureListener {
                    // This failure will happen if the barcode scanning model
                    // fails to download from Google Play Services
                }
                .addOnCompleteListener {
                    // When the image is from CameraX analysis use case, must
                    // call image.close() on received images when finished
                    // using them. Otherwise, new images may not be received
                    // or the camera may stall.
                    imageProxy.image?.close()
                    imageProxy.close()
                }
        }
    }

    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            mCameraProvider = cameraProvider
            // Preview
            val surfacePreview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(preview.surfaceProvider)
                }
            // Select back camera as a default
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()
                // Bind use cases to camera
                cameraProvider.bindToLifecycle(
                    activity,
                    cameraSelector,
                    surfacePreview,
                    analysisUseCase,
                )
            } catch (exc: Exception) {
                // Do nothing on exception
            }
        }, ContextCompat.getMainExecutor(context))
    }


}

In AndroidManifest.xml

<uses-permission android:name="android.permission.CAMERA" />

There are two problems which I am facing

Firstly I don’t see the camera permission when my screen opens even though I am asking the permission for it. I have to manually go to the settings screen to enable camera permission
And secondly the camera does not occupy entire screen

Even if I set linearLayout.layoutParams.height = 1000 still the height remains the same. I completely stopped the app and reran multiple times but no effect

Edit
I have created a gist where I have removed linear layout. Check here

3

Answers


  1. 1 Don’t occupy the entire screen.
    It’s very simple you might forget to set the width dynamically on android code because of that what happens is that android code gets only 400 widths and 400 heights (in
    lins linearLayout.layoutParams.width = 400
    linearLayout.layoutParams.height = 400
    ) and after the flutter call it receives only this size,
    so you must make it in an Adaptive way, see this answer.

    2 Don’t get permission:
    Simply using this package permission_handler will help and automate for you across all devices.

    If my answer helped you don’t forget to like it.

    Login or Signup to reply.
  2. check orientation and set it to portrait

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
      runApp(MyApp());
    ...
    }
    

    try weap your display area with

    SizedBox.expand(
      child: YOUR DISPLAY AREA,
    )
    

    also try

    Center(
      child: ,
    )
    
    Login or Signup to reply.
  3. I observed several things which might cause the behavior of your camera preview:

    1. You didn’t specify an aspect ratio for your (Preview- & ImageAnalysis-) Usecases. The default is 4:3. Note that the native aspect ratio of most cameras is indeed 4:3. If you use 16:9 the visible part of your image will be smaller, but it fits better for a common phone. E.g.:
    Preview.Builder().setTargetAspectRatio(AspectRatio.RATIO_16_9).build()
    
    1. You didn’t specify a scale type for your PreviewView. This also might result in image cropping. E.g.:
    preview.scaleType = PreviewView.ScaleType.FILL_CENTER
    
    1. The Camera orientation doesn’t fit to your screen. You should update the targetRotation of your usecases. You can read more on that topic in the CameraX documentation

    2. You should set the LayoutParams for your LinearLayout to MATCH_PARENT as well. And I also think you could just use a FrameLayout. Also remove the viewTreeObserver.

    3. You have to listen to the result of the permission request. You have to override the method onRequestPermissionsResult in your Activity. It will receive the result together with the request code you’ve specified: REQUEST_CODE_PERMISSIONS. However it might be easier to use a newer API for requesting those permissions: registerForPermissionResult. E.g.:

    val launcher = (context as ComponentActivity).registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
        if (granted) {
            startCamera()
        }
    }
    launcher.launch(android.Manifest.permission.CAMERA)
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search