Thursday, 2 February 2023

Native Camera View in Flutter does not occupy the whole width and height

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



from Native Camera View in Flutter does not occupy the whole width and height

No comments:

Post a Comment