diff --git a/.gitignore b/.gitignore index 3408e26..44cb7db 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,7 @@ android/.idea/ local.properties mparticle_flutter_sdk.iml -doc/ \ No newline at end of file +doc/ + +.build/ +.swiftpm/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cdfd17..c4f0f42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Make `android-rokt-kit` a `compileOnly` plugin dependency and guard Android Rokt registration behind a runtime classpath check, matching the optional Rokt kit pattern used in react-native-mparticle. + +## [2.2.0] - 2026-07-22 + +### Added + +- Add Swift Package Manager support for the iOS plugin (`ios/mparticle_flutter_sdk/Package.swift`). + +### Changed + +- Bump iOS CocoaPods dependency from `mParticle-Apple-SDK ~> 9.2` to `~> 9.3`. +- Bump Android Gradle dependencies to `android-core:6.0.0` and `android-rokt-kit:6.0.0`. +- **BREAKING**: Android `minSdkVersion` raised from `16` to `23` (required by Rokt SDK 6.x transitive dependencies). +- Migrate Android Rokt bridge code to Android SDK 6.0 APIs (`com.mparticle.kits`, `com.rokt.roktsdk`, `MParticle.rokt`). +- Replace deprecated `UserAttributeListener` with `TypedUserAttributeListener` on Android. + +### Removed + +- Android Rokt layout padding callbacks (`onMarginChanged`) — removed upstream in Android SDK 6.0. + ## [2.1.0] - 2026-05-19 ### Added diff --git a/MIGRATING.md b/MIGRATING.md index 9e84e01..70fe374 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -95,3 +95,34 @@ await MparticleFlutterSdk.getInstance().then((mp) => mp?.rokt.selectShoppableAds Rokt event delivery now uses explicit subscription by identifier through `Rokt.events(...)`. Call `events(identifier, ...)` before `selectPlacements(...)` or `selectShoppableAds(...)` for that identifier. The Rokt payment extension (for example `RoktPaymentExtension`) is **not** proxied through Dart. Integrators must add the pod and register it directly from native Swift/Objective-C in the host app (for example `ios/Runner/AppDelegate.swift`), after `MParticle.sharedInstance().start(with:)`. + +## Migrating Android apps to mParticle Android SDK 6.0.0 + +The Dart API is unchanged. Existing calls to `Rokt.selectPlacements`, `Rokt.events`, and `Rokt.purchaseFinalized` continue to use the same Flutter signatures and event channel names. + +### Android dependencies + +Android apps must include matching 6.x mParticle artifacts: + +```gradle +implementation "com.mparticle:android-core:6.0.0" +implementation "com.mparticle:android-rokt-kit:6.0.0" +``` + +`RoktEmbeddedView` and other Rokt bridge types moved out of `android-core` in SDK 6.0. They now live in `android-rokt-kit` under `com.mparticle.kits`. Apps that only add `android-core` will compile for non-Rokt usage, but Rokt method channel calls and `RoktLayout` platform views require `android-rokt-kit` on the app classpath. + +The Flutter plugin uses `compileOnly` for `android-rokt-kit` and registers Rokt platform views only when `com.mparticle.kits.RoktEmbeddedView` is present at runtime. + +Apps that include `android-rokt-kit` `6.0.0` should build with `compileSdk` 35+ and Android Gradle Plugin 8.6+. + +### Android Rokt API changes handled by the plugin + +| SDK 5.x | SDK 6.0 | +| --- | --- | +| `com.mparticle.rokt.RoktEmbeddedView` | `com.mparticle.kits.RoktEmbeddedView` | +| `MParticle.getInstance()?.Rokt()` | `MParticle.getInstance()?.rokt` | +| `com.mparticle.rokt.RoktConfig` | `com.rokt.roktsdk.RoktConfig` | +| `com.mparticle.RoktEvent` | `com.rokt.roktsdk.RoktEvent` | +| `UserAttributeListener` | `TypedUserAttributeListener` | + +`selectShoppableAds` remains a no-op on Android in this release. diff --git a/README.md b/README.md index 47e0426..63b4809 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,11 @@ To install mParticle on an Android platform: ```groovy dependencies { - implementation 'com.mparticle:android-core:5.79.0' - // Required only if you use Rokt APIs from Flutter - implementation 'com.mparticle:android-rokt-kit:5.79.0' + implementation 'com.mparticle:android-core:6.0.0' + + // Required if you use Rokt APIs or RoktLayout from Flutter. + // RoktEmbeddedView and related types live in android-rokt-kit, not android-core. + implementation 'com.mparticle:android-rokt-kit:6.0.0' // Required for gathering Android Advertising ID (see below) implementation 'com.google.android.gms:play-services-ads-identifier:16.0.0' @@ -56,6 +58,8 @@ dependencies { } ``` +> **Note:** The Flutter plugin compiles against `android-rokt-kit` but does not force it as a transitive dependency. Apps that use Rokt must add `android-rokt-kit` explicitly, matching the version of `android-core`. Without it, non-Rokt mParticle APIs continue to work, but Rokt method channel calls return an error. + 2. Grab your mParticle key and secret from [your workspace's dashboard](https://app.mparticle.com/setup/inputs/apps) and construct an `MParticleOptions` object. 3. Call `start` from the `onCreate` method of your app's `Application` class. It's crucial that the SDK be started here for proper session management. If you don't already have an `Application` class, create it and then specify its fully-qualified name in the `` tag of your app's `AndroidManifest.xml`. @@ -140,7 +144,7 @@ To install mParticle on an iOS platform: 2. Install the SDK using CocoaPods: ```bash -$ # Update your Podfile to depend on 'mParticle-Apple-SDK' version 9.2.0 or later +$ # Update your Podfile to depend on 'mParticle-Apple-SDK' version 9.3.1 or later $ pod install ``` diff --git a/android/build.gradle b/android/build.gradle index 03f35fe..45a669d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -2,7 +2,7 @@ group 'com.mparticle.mparticle_flutter_sdk' version '1.0-SNAPSHOT' buildscript { - ext.kotlin_version = '1.9.22' + ext.kotlin_version = '2.1.20' repositories { google() mavenCentral() @@ -41,14 +41,15 @@ android { } defaultConfig { - minSdkVersion 16 + minSdkVersion 23 compileSdkVersion 34 } } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - implementation 'com.mparticle:android-core:5.79.0' + implementation 'com.mparticle:android-core:6.0.0' + compileOnly 'com.mparticle:android-rokt-kit:6.0.0' // Required for Rokt event subscription implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.1" diff --git a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.kt b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.kt index d41e294..a2f1acb 100644 --- a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.kt +++ b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.kt @@ -2,8 +2,6 @@ package com.mparticle.mparticle_flutter_sdk import android.app.Activity import android.content.Context -import android.graphics.Typeface -import android.os.Build import androidx.annotation.NonNull import io.flutter.embedding.engine.plugins.FlutterPlugin @@ -20,22 +18,17 @@ import com.mparticle.identity.IdentityHttpResponse import com.mparticle.identity.MParticleUser import com.mparticle.MParticle import com.mparticle.MPEvent -import com.mparticle.UserAttributeListener +import com.mparticle.TypedUserAttributeListener import com.mparticle.WrapperSdk import com.mparticle.commerce.* import com.mparticle.consent.CCPAConsent import com.mparticle.consent.ConsentState import com.mparticle.consent.GDPRConsent -import com.mparticle.internal.Logger -import com.mparticle.rokt.CacheConfig -import com.mparticle.rokt.RoktConfig -import com.mparticle.rokt.RoktEmbeddedView import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding import org.json.JSONObject import kotlin.IllegalArgumentException -import java.lang.ref.WeakReference /** MparticleFlutterSdkPlugin */ @@ -46,25 +39,18 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware /// when the Flutter Engine is detached from the Activity private lateinit var channel: MethodChannel private val TAG = "MparticleFlutterSdkPlugin" - private lateinit var layoutFactory: RoktLayoutFactory + private var roktDelegate: RoktPluginDelegate? = null private var flutterAssets: FlutterPlugin.FlutterAssets? = null private var applicationContext: Context? = null private var activity: Activity? = null - private var roktEventHandler: RoktEventHandler? = null override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "mparticle_flutter_sdk") channel.setMethodCallHandler(this) - layoutFactory = RoktLayoutFactory(flutterPluginBinding.binaryMessenger) flutterAssets = flutterPluginBinding.flutterAssets applicationContext = flutterPluginBinding.applicationContext - flutterPluginBinding.platformViewRegistry.registerViewFactory( - VIEW_TYPE, - layoutFactory, - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - roktEventHandler = RoktEventHandler(flutterPluginBinding.binaryMessenger) - } + roktDelegate = RoktKitAvailability.createDelegate(flutterPluginBinding.binaryMessenger) + roktDelegate?.registerPlatformView(flutterPluginBinding.platformViewRegistry) } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { @@ -111,12 +97,13 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware } ?: result.error(TAG, "Missing attributeKey", null) } "getUserAttributes" -> this.getUser(call, result)?.let { - it.getUserAttributes(object : UserAttributeListener { + it.getUserAttributes(object : TypedUserAttributeListener { override fun onUserAttributesReceived( - userAttributes: Map?, - userAttributeLists: Map>?, mpid: Long? + userAttributes: Map, + userAttributeLists: Map?>, + mpid: Long, ) { - result.success(sanitizeMapToString(userAttributes)) + result.success(sanitizeMapToString(userAttributes.mapValues { (_, value) -> value?.toString() })) } }) Unit @@ -235,10 +222,18 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware setSdkVersion() result.success(true) } - "roktSubscribeToEvents" -> this.roktSubscribeToEvents(call, result) - "roktSelectPlacements" -> this.roktSelectPlacements(call, result) - "roktSelectShoppableAds" -> this.roktSelectShoppableAds(call, result) - "roktPurchaseFinalized" -> this.roktPurchaseFinalized(call, result) + "roktSubscribeToEvents" -> this.handleRoktCall(call, result) { delegate -> + delegate.subscribeToEvents(call, result, activity) + } + "roktSelectPlacements" -> this.handleRoktCall(call, result) { delegate -> + delegate.selectPlacements(call, result, applicationContext, flutterAssets) + } + "roktSelectShoppableAds" -> this.handleRoktCall(call, result) { delegate -> + delegate.selectShoppableAds(call, result) + } + "roktPurchaseFinalized" -> this.handleRoktCall(call, result) { delegate -> + delegate.purchaseFinalized(call, result) + } else -> { result.notImplemented() } @@ -720,119 +715,19 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware } ?: result.error(TAG, "No mParticle instance exists", null) } - private fun roktSelectPlacements(call: MethodCall, result: Result) { - try { - val placementId: String? = call.argument("placementId") - val attributes: Map? = call.argument("attributes") - val placeHolders: MutableMap> = mutableMapOf() - val configMap = call.argument>("config") - val config = configMap?.let { buildRoktConfig(it) } - val customFonts = call.argument>("fontFilePathMap") - .orEmpty() - .mapNotNull { (key, fontPath) -> - applicationContext?.assets?.let { assets -> - flutterAssets?.getAssetFilePathByName(fontPath)?.let { assetPath -> - runCatching { - key to WeakReference(Typeface.createFromAsset(assets, assetPath)) - }.getOrNull() - } - } - } - .toMap() - - call.argument>("placeholders")?.entries?.forEach { entry -> - layoutFactory.nativeViews[entry.key]?.let { view -> - placeHolders[entry.value] = WeakReference(view) - } - } - - if (placementId == null) { - result.error(TAG, "Missing placementId", null) - return - } - - val stringAttributes: MutableMap = mutableMapOf() - attributes?.forEach { (key, value) -> - stringAttributes[key] = value?.toString() ?: "" - } - - MParticle.getInstance()?.let { instance -> - instance.Rokt().selectPlacements(placementId, stringAttributes, null, placeHolders.takeIf { it.isNotEmpty() }, customFonts, config) - result.success(true) - } ?: result.error(TAG, "No mParticle instance exists", null) - } catch (e: Exception) { - result.error(TAG, e.localizedMessage, null) - } - } - - private fun roktSubscribeToEvents(call: MethodCall, result: Result) { - val identifier = call.argument("identifier") - if (identifier.isNullOrBlank()) { - result.error(TAG, "Missing identifier", null) + private fun handleRoktCall( + call: MethodCall, + result: Result, + handler: (RoktPluginDelegate) -> Unit, + ) { + val delegate = roktDelegate + if (delegate == null) { + result.error(TAG, RoktKitAvailability.REQUIRED_MESSAGE, null) return } - - MParticle.getInstance()?.let { instance -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - activity?.let { currentActivity -> - roktEventHandler?.subscribeToEvents( - events = instance.Rokt().events(identifier), - activity = currentActivity, - identifier = identifier, - ) - } - } - result.success(true) - } ?: result.error(TAG, "No mParticle instance exists", null) - } - - private fun buildRoktConfig(configMap: Map): RoktConfig { - val builder = RoktConfig.Builder() - (configMap["colorMode"] as? String)?.let { - builder.colorMode(it.toColorMode()) - } - (configMap["cacheConfig"] as? Map)?.let { cacheConfig -> - val cacheDurationInSeconds = cacheConfig["cacheDurationInSeconds"] as? Int ?: 0 - val cacheAttributes = cacheConfig["cacheAttributes"] as? Map ?: null - builder.cacheConfig(CacheConfig(cacheDurationInSeconds.toLong(), cacheAttributes)) - } - - return builder.build() - } - - private fun roktPurchaseFinalized(call: MethodCall, result: Result) { - val placementId = call.argument("placementId") - val catalogItemId = call.argument("catalogItemId") - val success = call.argument("success") ?: true - if (placementId != null && catalogItemId != null) { - MParticle.getInstance()?.Rokt()?.purchaseFinalized( - placementId = placementId, - catalogItemId = catalogItemId, - status = success, - ) - result.success("Success") - } else { - result.error( - "INVALID_PARAMS", - "placementId and catalogItemId are required", - null, - ) - } + handler(delegate) } - private fun roktSelectShoppableAds(call: MethodCall, result: Result) { - // Parity with RN bridge: Android API is exposed but not implemented yet. - Logger.warning("selectShoppableAds is not yet supported on Android") - result.success(true) - } - - private fun String.toColorMode(): RoktConfig.ColorMode = - when (this) { - "dark" -> RoktConfig.ColorMode.DARK - "light" -> RoktConfig.ColorMode.LIGHT - else -> RoktConfig.ColorMode.SYSTEM - } - private fun ConvertIdentityHttpResponseToString(response: IdentityHttpResponse?): String { val map = mutableMapOf() @@ -1005,8 +900,4 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware instance.setWrapperSdk(WrapperSdk.WrapperFlutter, "") } } - - companion object { - private const val VIEW_TYPE = "rokt_sdk.rokt.com/rokt_layout" - } } diff --git a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktBridge.kt b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktBridge.kt new file mode 100644 index 0000000..de7d6e1 --- /dev/null +++ b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktBridge.kt @@ -0,0 +1,166 @@ +package com.mparticle.mparticle_flutter_sdk + +import android.app.Activity +import android.content.Context +import android.graphics.Typeface +import android.os.Build +import com.mparticle.MParticle +import com.mparticle.internal.Logger +import com.mparticle.kits.RoktEmbeddedView +import com.mparticle.kits.rokt +import com.rokt.roktsdk.CacheConfig +import com.rokt.roktsdk.RoktConfig +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.platform.PlatformViewRegistry +import java.lang.ref.WeakReference + +internal class RoktBridge( + messenger: BinaryMessenger, +) : RoktPluginDelegate { + private val layoutFactory = RoktLayoutFactory(messenger) + private val roktEventHandler = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + RoktEventHandler(messenger) + } else { + null + } + + override fun registerPlatformView(registry: PlatformViewRegistry) { + registry.registerViewFactory(VIEW_TYPE, layoutFactory) + } + + override fun selectPlacements( + call: MethodCall, + result: MethodChannel.Result, + applicationContext: Context?, + flutterAssets: FlutterPlugin.FlutterAssets?, + ) { + try { + val placementId: String? = call.argument("placementId") + val attributes: Map? = call.argument("attributes") + val placeHolders: MutableMap> = mutableMapOf() + val configMap = call.argument>("config") + val config = configMap?.let { buildRoktConfig(it) } + val customFonts = call.argument>("fontFilePathMap") + .orEmpty() + .mapNotNull { (key, fontPath) -> + applicationContext?.assets?.let { assets -> + flutterAssets?.getAssetFilePathByName(fontPath)?.let { assetPath -> + runCatching { + key to WeakReference(Typeface.createFromAsset(assets, assetPath)) + }.getOrNull() + } + } + } + .toMap() + + call.argument>("placeholders")?.entries?.forEach { entry -> + layoutFactory.nativeViews[entry.key]?.let { view -> + placeHolders[entry.value] = WeakReference(view) + } + } + + if (placementId == null) { + result.error(TAG, "Missing placementId", null) + return + } + + val stringAttributes: MutableMap = mutableMapOf() + attributes?.forEach { (key, value) -> + stringAttributes[key] = value?.toString() ?: "" + } + + MParticle.getInstance()?.let { instance -> + instance.rokt.selectPlacements( + placementId, + stringAttributes, + placeHolders.takeIf { it.isNotEmpty() }, + customFonts.takeIf { it.isNotEmpty() }, + config, + ) + result.success(true) + } ?: result.error(TAG, "No mParticle instance exists", null) + } catch (e: Exception) { + result.error(TAG, e.localizedMessage, null) + } + } + + override fun subscribeToEvents( + call: MethodCall, + result: MethodChannel.Result, + activity: Activity?, + ) { + val identifier = call.argument("identifier") + if (identifier.isNullOrBlank()) { + result.error(TAG, "Missing identifier", null) + return + } + + MParticle.getInstance()?.let { instance -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + activity?.let { currentActivity -> + roktEventHandler?.subscribeToEvents( + events = instance.rokt.events(identifier), + activity = currentActivity, + identifier = identifier, + ) + } + } + result.success(true) + } ?: result.error(TAG, "No mParticle instance exists", null) + } + + override fun purchaseFinalized(call: MethodCall, result: MethodChannel.Result) { + val placementId = call.argument("placementId") + val catalogItemId = call.argument("catalogItemId") + val success = call.argument("success") ?: true + if (placementId != null && catalogItemId != null) { + MParticle.getInstance()?.rokt?.purchaseFinalized( + placementId, + catalogItemId, + success, + ) + result.success("Success") + } else { + result.error( + "INVALID_PARAMS", + "placementId and catalogItemId are required", + null, + ) + } + } + + override fun selectShoppableAds(call: MethodCall, result: MethodChannel.Result) { + Logger.warning("selectShoppableAds is not yet supported on Android") + result.success(true) + } + + private fun buildRoktConfig(configMap: Map): RoktConfig { + val builder = RoktConfig.Builder() + (configMap["colorMode"] as? String)?.let { + builder.colorMode(it.toColorMode()) + } + (configMap["cacheConfig"] as? Map)?.let { cacheConfig -> + val cacheDurationInSeconds = cacheConfig["cacheDurationInSeconds"] as? Int ?: 0 + val cacheAttributes = cacheConfig["cacheAttributes"] as? Map + builder.cacheConfig(CacheConfig(cacheDurationInSeconds.toLong(), cacheAttributes)) + } + + return builder.build() + } + + private fun String.toColorMode(): RoktConfig.ColorMode = + when (this) { + "dark" -> RoktConfig.ColorMode.DARK + "light" -> RoktConfig.ColorMode.LIGHT + else -> RoktConfig.ColorMode.SYSTEM + } + + private companion object { + private const val TAG = "MparticleFlutterSdkPlugin" + private const val VIEW_TYPE = "rokt_sdk.rokt.com/rokt_layout" + } +} diff --git a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktEventHandler.kt b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktEventHandler.kt index 563d6c7..32e1738 100644 --- a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktEventHandler.kt +++ b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktEventHandler.kt @@ -7,7 +7,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle -import com.mparticle.RoktEvent +import com.rokt.roktsdk.RoktEvent import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.EventChannel import kotlinx.coroutines.Job @@ -99,16 +99,16 @@ class RoktEventHandler(private val messenger: BinaryMessenger) { private val RoktEvent.placementId: String? get() = when (this) { - is RoktEvent.FirstPositiveEngagement -> placementId - is RoktEvent.OfferEngagement -> placementId - is RoktEvent.PlacementClosed -> placementId - is RoktEvent.PlacementCompleted -> placementId - is RoktEvent.PlacementFailure -> placementId - is RoktEvent.PlacementInteractive -> placementId - is RoktEvent.PlacementReady -> placementId - is RoktEvent.PositiveEngagement -> placementId - is RoktEvent.OpenUrl -> placementId - is RoktEvent.CartItemInstantPurchase -> placementId + is RoktEvent.FirstPositiveEngagement -> identifier + is RoktEvent.OfferEngagement -> identifier + is RoktEvent.PlacementClosed -> identifier + is RoktEvent.PlacementCompleted -> identifier + is RoktEvent.PlacementFailure -> identifier + is RoktEvent.PlacementInteractive -> identifier + is RoktEvent.PlacementReady -> identifier + is RoktEvent.PositiveEngagement -> identifier + is RoktEvent.OpenUrl -> identifier + is RoktEvent.CartItemInstantPurchase -> identifier else -> null } diff --git a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktKitAvailability.kt b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktKitAvailability.kt new file mode 100644 index 0000000..6c225ff --- /dev/null +++ b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktKitAvailability.kt @@ -0,0 +1,27 @@ +package com.mparticle.mparticle_flutter_sdk + +import io.flutter.plugin.common.BinaryMessenger + +internal object RoktKitAvailability { + const val REQUIRED_MESSAGE = + "Rokt is unavailable. Add com.mparticle:android-rokt-kit to your app's build.gradle." + + private const val ROKT_EMBEDDED_VIEW_CLASS = "com.mparticle.kits.RoktEmbeddedView" + + fun isAvailable(): Boolean = + try { + Class.forName(ROKT_EMBEDDED_VIEW_CLASS) + true + } catch (_: ClassNotFoundException) { + false + } catch (_: LinkageError) { + false + } + + fun createDelegate(messenger: BinaryMessenger): RoktPluginDelegate? { + if (!isAvailable()) { + return null + } + return RoktBridge(messenger) + } +} diff --git a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktLayout.kt b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktLayout.kt index 3664a28..3287e32 100644 --- a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktLayout.kt +++ b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktLayout.kt @@ -1,8 +1,8 @@ package com.mparticle.mparticle_flutter_sdk import android.content.Context -import com.mparticle.rokt.RoktEmbeddedView -import com.mparticle.rokt.RoktLayoutDimensionCallBack +import com.mparticle.kits.RoktEmbeddedView +import com.mparticle.kits.RoktLayoutDimensionCallBack import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.platform.PlatformView @@ -28,20 +28,6 @@ class RoktLayout( channel.invokeMethod(VIEW_HEIGHT_LISTENER, map) } - private fun sendUpdatedPadding( - left: Double, - top: Double, - right: Double, - bottom: Double, - ) { - val map: MutableMap = mutableMapOf() - map[VIEW_PADDING_LEFT] = left - map[VIEW_PADDING_TOP] = top - map[VIEW_PADDING_RIGHT] = right - map[VIEW_PADDING_BOTTOM] = bottom - channel.invokeMethod(VIEW_PADDING_LISTENER, map) - } - override fun getView(): RoktEmbeddedView? = layout override fun dispose() { @@ -51,12 +37,7 @@ class RoktLayout( companion object { private const val VIEW_HEIGHT_LISTENER = "viewHeightListener" - private const val VIEW_PADDING_LISTENER = "viewPaddingListener" private const val VIEW_HEIGHT_LISTENER_PARAM = "size" - private const val VIEW_PADDING_LEFT = "left" - private const val VIEW_PADDING_TOP = "top" - private const val VIEW_PADDING_RIGHT = "right" - private const val VIEW_PADDING_BOTTOM = "bottom" private const val OUT_OF_SYNC_HEIGHT_DIFF = 1 } @@ -66,13 +47,4 @@ class RoktLayout( sendUpdatedHeight(lastHeight.toDouble()) } } - - override fun onMarginChanged( - start: Int, - top: Int, - end: Int, - bottom: Int, - ) { - sendUpdatedPadding(start.toDouble(), top.toDouble(), end.toDouble(), bottom.toDouble()) - } } diff --git a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktLayoutFactory.kt b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktLayoutFactory.kt index 2b996c9..6ca7b85 100644 --- a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktLayoutFactory.kt +++ b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktLayoutFactory.kt @@ -1,7 +1,7 @@ package com.mparticle.mparticle_flutter_sdk import android.content.Context -import com.mparticle.rokt.RoktEmbeddedView +import com.mparticle.kits.RoktEmbeddedView import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.StandardMessageCodec import io.flutter.plugin.platform.PlatformView diff --git a/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktPluginDelegate.kt b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktPluginDelegate.kt new file mode 100644 index 0000000..45ebddb --- /dev/null +++ b/android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/RoktPluginDelegate.kt @@ -0,0 +1,29 @@ +package com.mparticle.mparticle_flutter_sdk + +import android.app.Activity +import android.content.Context +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.platform.PlatformViewRegistry + +internal interface RoktPluginDelegate { + fun registerPlatformView(registry: PlatformViewRegistry) + + fun selectPlacements( + call: MethodCall, + result: MethodChannel.Result, + applicationContext: Context?, + flutterAssets: FlutterPlugin.FlutterAssets?, + ) + + fun subscribeToEvents( + call: MethodCall, + result: MethodChannel.Result, + activity: Activity?, + ) + + fun purchaseFinalized(call: MethodCall, result: MethodChannel.Result) + + fun selectShoppableAds(call: MethodCall, result: MethodChannel.Result) +} diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index ee3af85..4457e47 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -49,7 +49,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.mparticle.mparticle_flutter_sdk_example" - minSdkVersion 21 + minSdkVersion 23 targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -70,9 +70,9 @@ flutter { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' - implementation 'com.mparticle:android-core:5.79.0' + implementation 'com.mparticle:android-core:6.0.0' - implementation 'com.mparticle:android-rokt-kit:5.79.0' + implementation 'com.mparticle:android-rokt-kit:6.0.0' // Required for gathering Android Advertising ID (see below) implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1' diff --git a/example/android/settings.gradle b/example/android/settings.gradle index bc95f9f..24c809a 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -20,7 +20,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.6.0" apply false - id "org.jetbrains.kotlin.android" version "1.9.23" apply false + id "org.jetbrains.kotlin.android" version "2.1.20" apply false } include ':app' diff --git a/example/pubspec.lock b/example/pubspec.lock index 2f9aa2a..130cd80 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" flutter: dependency: "direct main" description: flutter @@ -99,26 +99,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "11.0.2" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" matcher: dependency: transitive description: @@ -139,17 +139,17 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mparticle_flutter_sdk: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.0.0" + version: "2.1.0" path: dependency: transitive description: @@ -162,18 +162,18 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" process: dependency: transitive description: name: process - sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" url: "https://pub.dev" source: hosted - version: "5.0.2" + version: "5.0.3" sky_engine: dependency: transitive description: flutter @@ -183,10 +183,10 @@ packages: dependency: transitive description: name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" stack_trace: dependency: transitive description: @@ -207,10 +207,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.1" sync_http: dependency: transitive description: @@ -223,42 +223,42 @@ packages: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.4" vector_math: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "15.0.0" webdriver: dependency: transitive description: name: webdriver - sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.1.0" sdks: - dart: ">=3.8.0-0 <4.0.0" + dart: ">=3.7.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/ios/.gitignore b/ios/.gitignore index 0c88507..7b634ab 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -35,4 +35,7 @@ Icon? /Flutter/Generated.xcconfig /Flutter/ephemeral/ -/Flutter/flutter_export_environment.sh \ No newline at end of file +/Flutter/flutter_export_environment.sh + +.build/ +.swiftpm/ \ No newline at end of file diff --git a/ios/mparticle_flutter_sdk.podspec b/ios/mparticle_flutter_sdk.podspec index 4180c88..385c9af 100644 --- a/ios/mparticle_flutter_sdk.podspec +++ b/ios/mparticle_flutter_sdk.podspec @@ -13,10 +13,11 @@ mParticle Flutter Wrapper s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*' + s.source_files = 'mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/**/*.{h,m,swift}' + s.public_header_files = 'mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/include/**/*.h' s.dependency 'Flutter' - # SDK 9.2 umbrella pod pulls required transitive dependencies. - s.dependency 'mParticle-Apple-SDK', '~> 9.2' + # SDK 9.3 umbrella pod pulls required transitive dependencies. + s.dependency 'mParticle-Apple-SDK', '~> 9.3' s.platform = :ios, '15.6' # Flutter.framework does not contain a i386 slice. diff --git a/ios/mparticle_flutter_sdk/Package.swift b/ios/mparticle_flutter_sdk/Package.swift new file mode 100644 index 0000000..633ddd0 --- /dev/null +++ b/ios/mparticle_flutter_sdk/Package.swift @@ -0,0 +1,38 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "mparticle_flutter_sdk", + platforms: [ + .iOS("15.6"), + ], + products: [ + .library(name: "mparticle-flutter-sdk", targets: ["mparticle_flutter_sdk"]), + ], + dependencies: [ + .package(name: "FlutterFramework", path: "../FlutterFramework"), + .package( + url: "https://github.com/mParticle/mparticle-apple-sdk.git", + .upToNextMajor(from: "9.3.1") + ), + .package( + url: "https://github.com/ROKT/rokt-contracts-apple.git", + .upToNextMajor(from: "2.0.0") + ), + ], + targets: [ + .target( + name: "mparticle_flutter_sdk", + dependencies: [ + .product(name: "FlutterFramework", package: "FlutterFramework"), + .product(name: "mParticle-Apple-SDK", package: "mparticle-apple-sdk"), + .product(name: "RoktContracts", package: "rokt-contracts-apple"), + ], + cSettings: [ + .headerSearchPath("include/mparticle_flutter_sdk"), + ] + ), + ] +) diff --git a/ios/Classes/FLRoktLayoutView.swift b/ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/FLRoktLayoutView.swift similarity index 100% rename from ios/Classes/FLRoktLayoutView.swift rename to ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/FLRoktLayoutView.swift diff --git a/ios/Classes/MparticleFlutterSdkPlugin.m b/ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.m similarity index 100% rename from ios/Classes/MparticleFlutterSdkPlugin.m rename to ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.m diff --git a/ios/Classes/RoktEventHandler.swift b/ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/RoktEventHandler.swift similarity index 100% rename from ios/Classes/RoktEventHandler.swift rename to ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/RoktEventHandler.swift diff --git a/ios/Classes/RoktLayoutFactory.swift b/ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/RoktLayoutFactory.swift similarity index 100% rename from ios/Classes/RoktLayoutFactory.swift rename to ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/RoktLayoutFactory.swift diff --git a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift b/ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/SwiftMparticleFlutterSdkPlugin.swift similarity index 100% rename from ios/Classes/SwiftMparticleFlutterSdkPlugin.swift rename to ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/SwiftMparticleFlutterSdkPlugin.swift diff --git a/ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/include/mparticle_flutter_sdk/.gitkeep b/ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/include/mparticle_flutter_sdk/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ios/Classes/MparticleFlutterSdkPlugin.h b/ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/include/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.h similarity index 100% rename from ios/Classes/MparticleFlutterSdkPlugin.h rename to ios/mparticle_flutter_sdk/Sources/mparticle_flutter_sdk/include/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.h diff --git a/lib/mparticle_flutter_sdk.dart b/lib/mparticle_flutter_sdk.dart index c0b8251..e362087 100644 --- a/lib/mparticle_flutter_sdk.dart +++ b/lib/mparticle_flutter_sdk.dart @@ -317,7 +317,7 @@ class Rokt { /// /// This method calls the Rokt selectPlacements API on each platform: /// - Web: mParticle.Rokt.selectPlacements() - /// - Android: MParticle.getInstance()?.Rokt().selectPlacements() + /// - Android: MParticle.getInstance()?.rokt.selectPlacements() /// - iOS: MParticle.sharedInstance().rokt.selectPlacements() Future selectPlacements({ required String identifier,