Skip to content

[image_picker] Image picker plugin is not working when there is custom platform channel code in MainActivity.kt #67412

@RanaOsamaAsif

Description

@RanaOsamaAsif

I'm using platform channels to integrate a payment gateway SDK in my flutter application. I implemented the payment SDK code in my Flutter application's MainActivity.kt and call that code via platform channel which launches the checkout page and completes the payment process.

The problem is that when there is code in the MainActivity.kt the image picker stops working. The picker window is opened but clicking on any image does nothing. When I comment out the code in the MainActivity.kt the image picker starts to work as expected.

Another weird behavior that I have observed is that when the application is cold launched the plugin opens the picker window(However the image is still not selected), closing and reopening the picker again throws the following exception

[ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: PlatformException(already_active, Image picker is already active, null)

The payment gateway SDK has the following dependencies which I have added in the app level gradle file(All of these are latest and upgraded as per need of androidx migrations)

implementation 'androidx.appcompat:appcompat:1.3.0-alpha02'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.browser:browser:1.2.0'
implementation 'com.google.android.gms:play-services-wallet:18.1.1'
implementation 'io.card:android-sdk:5.5.1'

Version details are as follows

flutter: 1.20.4(Stable)
image_picker: ^0.6.7+11

module:
  androidX: true

Here is my MainActivity.kt

import android.content.Intent
import com.oppwa.mobile.connect.checkout.dialog.CheckoutActivity
import com.oppwa.mobile.connect.checkout.meta.CheckoutSettings
import com.oppwa.mobile.connect.exception.PaymentError
import com.oppwa.mobile.connect.provider.Connect
import com.oppwa.mobile.connect.provider.Transaction
import com.oppwa.mobile.connect.provider.TransactionType
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    internal var resourcePath: String? = null
    internal var mResult: MethodChannel.Result? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
                .setMethodCallHandler { call, result ->
                    if (call.method == "hyperpay") {
                        mResult = result
                        openCheckoutUI(call.argument<String>("checkoutID"))
                    }
                }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)
        setIntent(intent)
        if (resourcePath != null && hasCallbackScheme(intent)) {
            mResult?.success("200")
        }
    }

    private fun hasCallbackScheme(intent: Intent): Boolean {
        val scheme = intent.scheme
        return "checkoutui" == scheme
    }

    private fun openCheckoutUI(checkoutId: String?) {
        val checkoutSettings = createCheckoutSettings(checkoutId, "checkoutui")

        /* Set up the Intent and start the checkout activity. */
        val intent = checkoutSettings.createCheckoutActivityIntent(this)

        startActivityForResult(intent, CheckoutActivity.REQUEST_CODE_CHECKOUT)
    }

    private fun createCheckoutSettings(checkoutId: String?, callbackScheme: String): CheckoutSettings {
        return CheckoutSettings(checkoutId!!, Constants.Config.PAYMENT_BRANDS,
                Connect.ProviderMode.TEST)
                .setShopperResultUrl("$callbackScheme://callback")
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
        /* Override onActivityResult to get notified when the checkout process is done. */
        if (requestCode == CheckoutActivity.REQUEST_CODE_CHECKOUT) {
            when (resultCode) {
                CheckoutActivity.RESULT_OK -> {
                    /* Transaction completed. */
                    val transaction = data.getParcelableExtra<Transaction>(
                            CheckoutActivity.CHECKOUT_RESULT_TRANSACTION)

                    resourcePath = data.getStringExtra(
                            CheckoutActivity.CHECKOUT_RESULT_RESOURCE_PATH)

                    /* Check the transaction type. */
                    if (transaction.transactionType == TransactionType.SYNC) {
                        mResult?.success("201")
                    }
                }
                CheckoutActivity.RESULT_CANCELED -> {

                    mResult?.success("403")
                }
                CheckoutActivity.RESULT_ERROR -> {
                    val error = data.getParcelableExtra<PaymentError>(
                            CheckoutActivity.CHECKOUT_RESULT_ERROR)
                    mResult?.error("400", error.errorMessage,error.errorInfo)

                }
            }
        }
    }

    companion object {
        private val CHANNEL = "com.abc.xyz/hyperpay"
    }

}

Here is my Manifest

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.abc.xyz>

    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <uses-permission android:name="android.permission.INTERNET"/>
     <!-- Permissions options for the `storage` group -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <!-- Permissions options for the `camera` group -->
    <uses-permission android:name="android.permission.CAMERA"/>
    
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="App Name"
        android:requestLegacyExternalStorage="true"
        android:icon="@mipmap/ic_launcher">

        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
        <activity
            android:name="com.oppwa.mobile.connect.checkout.dialog.CheckoutActivity"

            android:windowSoftInputMode="adjustResize"
            android:exported="false"
            android:launchMode="singleTop"/>
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- This keeps the window background of the activity showing
                 until Flutter renders its first frame. It can be removed if
                 there is no splash screen (such as the default splash screen
                 defined in @style/LaunchTheme). -->
            <meta-data
                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
                android:value="true" />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <intent-filter>
            <!-- OPPWA Payment Gateway Integration -->
                <data android:scheme="checkoutui"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
            </intent-filter>
            <!-- Firebase Cloud Messaging/Notifications -->
            <intent-filter>
                <action android:name="FLUTTER_NOTIFICATION_CLICK" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Flutter doctor output

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 1.20.4, on Mac OS X 10.15.7 19H2, locale en-US)
 
[!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor
      --android-licenses
 
[✓] Xcode - develop for iOS and macOS (Xcode 12.0.1)
[!] Android Studio (version 3.5)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] VS Code (version 1.49.3)
 
[✓] Connected device (1 available)            

! Doctor found issues in 2 categories.

Metadata

Metadata

Assignees

No one assigned

    Labels

    in triagePresently being triaged by the triage team

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions