Multi_Image_Picker_Plus throws already_active exception on Android

114 views Asked by At

We're using Multi Image Picker Plus in our Flutter App to pick multiple images with max number of images. Now before this package we're using MultiImagePicker2 which is depreciated and is no longer maintained so we decided to replace it with the closest plugin.

Someone updated it and also converted the Java code to Kotlin.

For some reason when we try to convert the Asset file to File by the below method,

Future<List<File>> getImageFileFromAssets(List<Asset> imageFiles) async {
    List<File> files = [];
    try{

      await Future.wait(imageFiles.map((asset) async {

        /// Generate a unique filename to avoid overwriting.
        final uniqueFileName = "${DateTime.now().millisecondsSinceEpoch}_${asset.name}";

        final byteData = await asset.getByteData(quality: 85);

        final tempFile = Platform.isAndroid ? File("${(await getTemporaryDirectory()).path}/${asset.name}") : File("${(await getTemporaryDirectory()).path}/$uniqueFileName");

        final file = await tempFile.writeAsBytes(
          byteData.buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes),
        );

        /// **RIGHT ABOUT HERE THE PLATFORM EXCEPTION IS THROWN**

        files.add(file);

      }));

    } catch (e){
      print("getImageFileFromAssets exception -> ${e.toString()}");
      rethrow;
    }
    return files;
  }

It throws error,

Platform Exception ("already_active", "Image picker is already active")

Now I've tried to narrow down the problem in native code (with my newbie knowledge to Kotlin) and figured that for some reason the code below throws this error when the methodCall requestOriginal is made AGAIN (I may be wrong).

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
        if (!setPendingMethodCallAndResult(call, result)) {
            finishWithAlreadyActiveError(result)
            return
        }

        if (pickImages == call.method) {
            val options = call.argument<HashMap<String, String>>(androidOptions)!!
            val selectedAssets = methodCall!!.argument<ArrayList<String>>(selectedAssets)!!
            val maxImages = methodCall!!.argument<Int>(maxImages)!!
            presentPicker(maxImages, selectedAssets, options)
        } else if (requestOriginal == call.method) {
            val identifier = call.argument<String>("identifier")
            val quality = call.argument<Any>("quality") as Int
            if (!this.uriExists(identifier!!)) {
                finishWithError("ASSET_DOES_NOT_EXIST", "The requested image does not exist.")
            } else {
                val scope = CoroutineScope(Dispatchers.Main)
                scope.launch(Dispatchers.Main) {
                    val buffer = getImage(identifier, quality)
                    if (buffer != null) {
                        messenger?.send("multi_image_picker_plus/image/$identifier.original", buffer)
                        buffer.clear()
                    }
                    finishWithSuccess()
                }
            }
        } else if (requestThumbnail == call.method) {
            Log.d("CoroutineDebug", "requestThumbnail")
            val identifier = call.argument<String>("identifier")
            val quality = call.argument<Any>("quality") as Int
            if (!this.uriExists(identifier!!)) {
                finishWithError("ASSET_DOES_NOT_EXIST", "The requested image does not exist.")
            } else {
                val scope = CoroutineScope(Dispatchers.Main)
                scope.launch(Dispatchers.Main) {
                    val buffer = getThumbnail(identifier, quality)
                    if (buffer != null) {
                        messenger!!.send("multi_image_picker_plus/image/$identifier.thumb", buffer)
                        buffer.clear()
                        finishWithSuccess()
                    }
                }
                finishWithSuccess()
            }
        } else {
            pendingResult!!.notImplemented()
            clearMethodCallAndResult()
        }
    }

The methods and variables used in the above code are:


private var channel: MethodChannel? = null
    private var activity: Activity? = null
    private val channelName = "multi_image_picker_plus"
    private val requestThumbnail = "requestThumbnail"
    private val requestOriginal = "requestOriginal"
    private val requestMetadata = "requestMetadata"
    private val pickImages = "pickImages"
    private val maxImages = "maxImages"
    private val selectedAssets = "selectedAssets"
    private val androidOptions = "androidOptions"
    private val requestCodeChoose = 1001
    private var context: Context? = null
    private var messenger: BinaryMessenger? = null
    private var pendingResult: Result? = null
    private var methodCall: MethodCall? = null

 private fun finishWithSuccess(imagePathList: List<*>) {
        if (pendingResult != null) pendingResult!!.success(imagePathList)
        clearMethodCallAndResult()
    }

    private fun finishWithSuccess(hashMap: HashMap<String, Any?>) {
        if (pendingResult != null) pendingResult!!.success(hashMap)
        clearMethodCallAndResult()
    }

    private fun finishWithSuccess() {
        if (pendingResult != null) pendingResult!!.success(true)
        clearMethodCallAndResult()
    }

    private fun finishWithAlreadyActiveError(result: Result?) {
        result?.error("already_active", "Image picker is already active", null)
    }

    private fun finishWithError(errorCode: String, errorMessage: String) {
        if (pendingResult != null) pendingResult!!.error(errorCode, errorMessage, null)
        clearMethodCallAndResult()
    }

    private fun clearMethodCallAndResult() {
        methodCall = null
        pendingResult = null
    }

    private fun setPendingMethodCallAndResult(
        methodCall: MethodCall, result: Result
    ): Boolean {
        if (pendingResult != null) {
            return false
        }
        this.methodCall = methodCall
        pendingResult = result
        return true
    }


    private suspend fun getImage(
        identifier: String,
        quality: Int
    ): ByteBuffer? = withContext(Dispatchers.IO) {
        val uri = Uri.parse(identifier)
        var bytesArray: ByteArray? = null
        try {
            if (activity == null || activity!!.isFinishing) return@withContext null
            val bitmap: Bitmap =
                getCorrectlyOrientedImage(activity!!, uri)
                    ?: return@withContext null
            val bitmapStream = ByteArrayOutputStream()
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bitmapStream)
            bytesArray = bitmapStream.toByteArray()
            bitmap.recycle()
        } catch (e: IOException) {
//            Log.e("CoroutineDebug", "getImage Exception: ${e.message}")
            e.printStackTrace()
        }
        assert(bytesArray != null)
        if (bytesArray == null) {
            return@withContext null
        }
        val buffer = ByteBuffer.allocateDirect(bytesArray.size)
        buffer.put(bytesArray)
        return@withContext buffer
    }

I tried contacting the package dev but I guess they are busy somewhere or prolly not maintaining the package anymore.

Can someone please tell me how can I handle it or what am I doing wrong here? I've been stuck for days on this.

I tried changing things natively as well as added delay in my getBytes() call on Flutter side hoping that'll help or something but I don't know Kotlin so I couldn't do anything productive.

0

There are 0 answers