diff --git a/README.md b/README.md
index 8716117..f91df60 100644
--- a/README.md
+++ b/README.md
@@ -45,3 +45,9 @@ await MonextPayment.startPayment(
`ArgumentError` if `sessionToken` is empty, and a
`MonextPaymentChannelUnavailableException` if no native handler is registered for the
`MethodChannel`.
+
+## Try it
+
+The [`example`](example) app is a runnable test screen with a session token field, an environment
+picker and a **Buy** button wired to `startPayment`. See [`example/README.md`](example/README.md)
+for how to run it and get a test session token.
diff --git a/android/build.gradle.kts b/android/build.gradle.kts
index 726a661..07e2922 100644
--- a/android/build.gradle.kts
+++ b/android/build.gradle.kts
@@ -23,13 +23,22 @@ allprojects {
plugins {
id("com.android.library")
- id("org.jetbrains.kotlin.android")
- // Unlike the plugins above (bundled in kotlin-gradle-plugin/AGP and already resolvable via
- // the buildscript classpath below without a version), the Compose compiler is a separate
+ // Unlike the plugin below (bundled in kotlin-gradle-plugin/AGP and already resolvable via
+ // the buildscript classpath above without a version), the Compose compiler is a separate
// Gradle Plugin Portal artifact and needs an explicit version here to resolve.
id("org.jetbrains.kotlin.plugin.compose") version "2.3.20"
}
+// AGP 9+ ships Kotlin support built in, so applying the Kotlin Gradle Plugin (KGP) directly is
+// both redundant and, per Flutter's migration guide, will eventually break consuming apps' builds
+// (Flutter is dropping support for plugins that apply KGP). Only apply it for AGP < 9, which has
+// no built-in Kotlin support of its own.
+// See https://docs.flutter.dev/release/breaking-changes/migrate-to-built-in-kotlin/for-plugin-authors
+val agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.substringBefore('.').toInt()
+if (agpMajor < 9) {
+ apply(plugin = "org.jetbrains.kotlin.android")
+}
+
android {
namespace = "io.lenra.monext_payment"
@@ -58,6 +67,10 @@ android {
defaultConfig {
// Monext's Android SDK requires Android 8.0 (API 26) or later.
minSdk = 26
+
+ // See consumer-rules.pro: Compose UI's optional Android XR support otherwise fails full
+ // R8 minification ("Missing classes detected") for any app consuming this plugin.
+ consumerProguardFiles("consumer-rules.pro")
}
testOptions {
diff --git a/android/consumer-rules.pro b/android/consumer-rules.pro
new file mode 100644
index 0000000..d83ad71
--- /dev/null
+++ b/android/consumer-rules.pro
@@ -0,0 +1,11 @@
+# Jetpack Compose UI's optional Android XR (SceneCore/SplitEngine) support code references
+# OEM-only XR extension classes (com.android.extensions.xr.*) that don't ship in the standard
+# Android SDK/on non-XR devices. Full-mode R8 fails minification with "Missing classes detected"
+# for apps consuming this plugin (which pulls in Compose UI to host Monext's PaymentBox) even
+# though this code path is never reached on a normal device. See
+# https://issuetracker.google.com/issues/326315287 and Flutter/AGP's own "Missing classes
+# detected while running R8" guidance: add -dontwarn rather than a fully qualified keep rule,
+# since these classes intentionally aren't on the classpath.
+-dontwarn com.android.extensions.xr.**
+-dontwarn com.google.androidxr.splitengine.**
+-dontwarn com.google.imp.splitengine.**
diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 4e3b56d..1460d25 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -1,10 +1,14 @@
-
+
+ android:theme="@style/Theme.MonextPayment.Transparent" />
diff --git a/android/src/main/kotlin/io/lenra/monext_payment/MonextPaymentPlugin.kt b/android/src/main/kotlin/io/lenra/monext_payment/MonextPaymentPlugin.kt
index bb9d960..45c0f2f 100644
--- a/android/src/main/kotlin/io/lenra/monext_payment/MonextPaymentPlugin.kt
+++ b/android/src/main/kotlin/io/lenra/monext_payment/MonextPaymentPlugin.kt
@@ -1,9 +1,7 @@
package io.lenra.monext_payment
import android.app.Activity
-import androidx.activity.ComponentActivity
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContract
+import android.content.Intent
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
@@ -11,13 +9,15 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
+import io.flutter.plugin.common.PluginRegistry.ActivityResultListener
import org.json.JSONObject
/** MonextPaymentPlugin */
class MonextPaymentPlugin :
FlutterPlugin,
MethodCallHandler,
- ActivityAware {
+ ActivityAware,
+ ActivityResultListener {
// The MethodChannel that will the communication between Flutter and native Android
//
// This local reference serves to register the plugin with the Flutter Engine and unregister it
@@ -31,12 +31,20 @@ class MonextPaymentPlugin :
// plugin's public API.
internal lateinit var sdkClient: MonextSdkClient
- // Launches PaymentActivity and delivers its result. Only available while an Activity is
- // attached (see onAttachedToActivity/onDetachedFromActivity); registered via
- // ActivityResultRegistry directly (rather than ComponentActivity.registerForActivityResult)
- // because the plugin doesn't control the host Activity's onCreate, so it can't rely on
- // registering before the Activity reaches STARTED.
- private var paymentLauncher: ActivityResultLauncher? = null
+ // The Activity currently hosting the Flutter engine, and the binding used to (un)register this
+ // plugin as an ActivityResultListener. Only available while an Activity is attached (see
+ // onAttachedToActivity/onDetachedFromActivity).
+ //
+ // Launching PaymentActivity goes through the legacy Activity.startActivityForResult() /
+ // PluginRegistry.ActivityResultListener APIs rather than AndroidX's ActivityResultRegistry:
+ // the latter requires the host Activity to be a ComponentActivity, but
+ // io.flutter.embedding.android.FlutterActivity — the Activity subclass used by the vast
+ // majority of Flutter apps, including this plugin's own example app — extends plain
+ // android.app.Activity, not ComponentActivity. Relying on ActivityResultRegistry meant
+ // startPayment() failed with "activity_unavailable" on every single app using a stock
+ // FlutterActivity.
+ private var activity: Activity? = null
+ private var activityBinding: ActivityPluginBinding? = null
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "monext_payment")
@@ -75,8 +83,8 @@ class MonextPaymentPlugin :
return
}
- val launcher = paymentLauncher
- if (launcher == null) {
+ val currentActivity = activity
+ if (currentActivity == null) {
result.error(
"activity_unavailable",
"No Android Activity is attached to host the payment UI",
@@ -89,14 +97,16 @@ class MonextPaymentPlugin :
val googlePayConfigurationJson =
call.argument