From 1c29fcb46501fe8e8eeede8c69c63ceaa21b7459 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Thu, 7 May 2026 13:04:52 -0400 Subject: [PATCH 1/8] chore: update iOS native dependency constraints Align the Flutter iOS podspec and example Podfile with the latest mParticle Apple SDK guidance and keep installation docs in sync with the new minimum version. --- README.md | 2 +- example/ios/Podfile | 1 - ios/mparticle_flutter_sdk.podspec | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0a3f868..abbb86b 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,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 8.5.0 or later +$ # Update your Podfile to depend on 'mParticle-Apple-SDK' version 9.1.0 or later $ pod install ``` diff --git a/example/ios/Podfile b/example/ios/Podfile index 9998a56..bcd4e09 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -31,7 +31,6 @@ target 'Runner' do use_frameworks! use_modular_headers! - pod 'mParticle-Apple-SDK' pod 'mParticle-Rokt' flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) diff --git a/ios/mparticle_flutter_sdk.podspec b/ios/mparticle_flutter_sdk.podspec index a0d5366..69c2687 100644 --- a/ios/mparticle_flutter_sdk.podspec +++ b/ios/mparticle_flutter_sdk.podspec @@ -15,8 +15,8 @@ mParticle Flutter Wrapper s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - # SDK 9.0 umbrella pod pulls required transitive dependencies. - s.dependency 'mParticle-Apple-SDK', '~> 9.0' + # SDK 9.1 umbrella pod pulls required transitive dependencies. + s.dependency 'mParticle-Apple-SDK', '~> 9.1' s.platform = :ios, '15.6' # Flutter.framework does not contain a i386 slice. From 41b2170dfd2044279832ab0a521a5964aa628684 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Thu, 7 May 2026 14:18:53 -0400 Subject: [PATCH 2/8] feat: add explicit Rokt event subscription bridge Add a dedicated roktSubscribeToEvents bridge path and make selectPlacements rely on explicit event subscription to avoid implicit duplicate listener registration. --- .../MparticleFlutterSdkPlugin.kt | 32 +++++++++++++------ example/lib/main.dart | 3 ++ .../SwiftMparticleFlutterSdkPlugin.swift | 11 +++++-- lib/mparticle_flutter_sdk.dart | 7 ++++ 4 files changed, 41 insertions(+), 12 deletions(-) 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 8e3aa6e..d41e294 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 @@ -235,6 +235,7 @@ 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) @@ -756,16 +757,6 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware } MParticle.getInstance()?.let { instance -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - activity?.let { - roktEventHandler?.subscribeToEvents( - events = instance.Rokt().events(placementId), - activity = it, - identifier = placementId, - ) - } - } - instance.Rokt().selectPlacements(placementId, stringAttributes, null, placeHolders.takeIf { it.isNotEmpty() }, customFonts, config) result.success(true) } ?: result.error(TAG, "No mParticle instance exists", null) @@ -774,6 +765,27 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware } } + private fun roktSubscribeToEvents(call: MethodCall, result: Result) { + 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) + } + private fun buildRoktConfig(configMap: Map): RoktConfig { val builder = RoktConfig.Builder() (configMap["colorMode"] as? String)?.let { diff --git a/example/lib/main.dart b/example/lib/main.dart index 1403f85..091b7b0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -65,6 +65,9 @@ class _MyAppState extends State { // Platform messages are asynchronous, so we initialize in an async method. Future initMparticle() async { mpInstance = await MparticleFlutterSdk.getInstance(); + mpInstance?.rokt.events('MSDKEmbeddedLayout', (event) { + print("Rokt event: $event"); + }); if (mpInstance != null) { setState(() { _isInitialized = true; diff --git a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift index a02658b..3ef7846 100644 --- a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift @@ -508,6 +508,15 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin { } case "setSdkVersion": MParticle._setWrapperSdk_internal(MPWrapperSdk.flutter, version: "") + case "roktSubscribeToEvents": + if let callArguments = call.arguments as? [String: Any], + let identifier = callArguments["identifier"] as? String { + roktEventHandler.subscribeToEvents(identifier: identifier) + result(true) + } else { + print("Incorrect argument for \(call.method) iOS method") + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing identifier", details: nil)) + } case "roktSelectPlacements": if let callArguments = call.arguments as? [String: Any], let placementId = callArguments["placementId"] as? String { @@ -531,8 +540,6 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin { registerPartnerFonts(typefaces) } - roktEventHandler.subscribeToEvents(identifier: placementId) - MParticle.sharedInstance().rokt.selectPlacements(placementId, attributes: attributes, embeddedViews: placeholders, config: roktConfig) { [weak self] event in guard let self else { return } if let sizeChangedEvent = event as? RoktEvent.EmbeddedSizeChanged, diff --git a/lib/mparticle_flutter_sdk.dart b/lib/mparticle_flutter_sdk.dart index b629ca0..c0b8251 100644 --- a/lib/mparticle_flutter_sdk.dart +++ b/lib/mparticle_flutter_sdk.dart @@ -305,6 +305,13 @@ class Rokt { static const MethodChannel _channel = const MethodChannel('mparticle_flutter_sdk'); + static const EventChannel _eventChannel = EventChannel('MPRoktEvents'); + + /// Subscribes to Rokt events for a specific [identifier]. + void events(String identifier, void Function(dynamic event) onEvent) { + _channel.invokeMethod('roktSubscribeToEvents', {'identifier': identifier}); + _eventChannel.receiveBroadcastStream().listen(onEvent); + } /// Selects placements with a [identifier], optional [attributes], optional [roktConfig], and optional [fontFilePathMap]. /// From 4facd61ee07b86cd1fdd2ea12ec1c8fdeb4cbea3 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Thu, 7 May 2026 14:21:22 -0400 Subject: [PATCH 3/8] fix: remove implicit shoppable ads event subscription Stop auto-subscribing to Rokt events inside selectShoppableAds so event delivery consistently relies on explicit subscription APIs. --- ios/Classes/SwiftMparticleFlutterSdkPlugin.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift index 3ef7846..39ed571 100644 --- a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift @@ -577,7 +577,6 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin { roktConfig = buildRoktConfig(configMap: configMap) } - roktEventHandler.subscribeToEvents(identifier: identifier) MParticle.sharedInstance().rokt.selectShoppableAds( identifier, attributes: attributes, From 64d0b08d0082f5179c8f32552cb5f3eb07069e4a Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Fri, 8 May 2026 11:51:13 -0400 Subject: [PATCH 4/8] feat: add shoppable ads button to Flutter example Add an iOS-only Select Shoppable Ads action in the Rokt example screen and send the same attribute payload used in the MAUI sample for parity testing. --- example/lib/rokt_layouts_screen.dart | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/example/lib/rokt_layouts_screen.dart b/example/lib/rokt_layouts_screen.dart index 284d8bc..935a824 100644 --- a/example/lib/rokt_layouts_screen.dart +++ b/example/lib/rokt_layouts_screen.dart @@ -48,6 +48,30 @@ class _RoktLayoutsScreenState extends State { return {}; } + Map _getShoppableAdsAttributes() { + return { + 'country': 'US', + 'shippingstate': 'NY', + 'shippingzipcode': '10001', + 'firstname': 'Jenny', + 'stripeApplePayAvailable': 'true', + 'last4digits': '4444', + 'shippingaddress1': '123 Main St', + 'colormode': 'LIGHT', + 'billingzipcode': '07762', + 'paymenttype': 'ApplePay', + 'shippingcountry': 'US', + 'sandbox': 'true', + 'shippingaddress2': 'Apt 4B', + 'confirmationref': 'ORD-12345', + 'shippingcity': 'New York', + 'newToApplePay': 'false', + 'applePayCapabilities': 'true', + 'lastname': 'Smith', + 'email': 'jenny.smith@example.com', + }; + } + String _getPlatform() { if (kIsWeb) { return 'Web'; @@ -151,6 +175,30 @@ class _RoktLayoutsScreenState extends State { print('Error calling Rokt selectPlacements: $e'); } }), + const SizedBox(height: 20), + buildButton('Select Shoppable Ads (iOS)', () async { + if (!Platform.isIOS) { + print('${_getPlatform()} SelectShoppableAds is a no-op'); + return; + } + + var identityRequest = MparticleFlutterSdk.identityRequest; + identityRequest.setIdentity( + identityType: IdentityType.CustomerId, + value: _getIdentityValue()); + + try { + await widget.mpInstance?.identity + .identify(identityRequest: identityRequest); + + widget.mpInstance?.rokt.selectShoppableAds( + identifier: 'MSDKShoppableAdsLayout', + attributes: _getShoppableAdsAttributes()); + print('${_getPlatform()} Rokt selectShoppableAds called'); + } catch (e) { + print('Error calling Rokt selectShoppableAds: $e'); + } + }), Center( child: Text("Location1", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), ), From 70619a6d6b0220b75c66589c93f6bfc6a146734c Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Fri, 8 May 2026 13:07:11 -0400 Subject: [PATCH 5/8] chore: align example runtime setup for rokt validation Update the example app runtime configuration so placement and shoppable ads validation uses the current test identifiers and iOS payment extension dependency. --- example/android/app/build.gradle | 2 +- example/ios/Podfile | 1 + .../xcshareddata/xcschemes/Runner.xcscheme | 3 + example/lib/main.dart | 2 +- example/lib/rokt_layouts_screen.dart | 2 +- example/pubspec.lock | 60 ++++++++--------- pubspec.lock | 66 +++++++++---------- 7 files changed, 70 insertions(+), 66 deletions(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index c7b540d..39f889c 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 flutter.minSdkVersion targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/example/ios/Podfile b/example/ios/Podfile index bcd4e09..e307c40 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -32,6 +32,7 @@ target 'Runner' do use_modular_headers! pod 'mParticle-Rokt' + pod 'RoktPaymentExtension' flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 5e31d3d..9c12df5 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -26,6 +26,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit" shouldUseLaunchSchemeArgsEnv = "YES"> diff --git a/example/lib/main.dart b/example/lib/main.dart index 091b7b0..d12f66d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -65,7 +65,7 @@ class _MyAppState extends State { // Platform messages are asynchronous, so we initialize in an async method. Future initMparticle() async { mpInstance = await MparticleFlutterSdk.getInstance(); - mpInstance?.rokt.events('MSDKEmbeddedLayout', (event) { + mpInstance?.rokt.events('StgRoktShoppableAds', (event) { print("Rokt event: $event"); }); if (mpInstance != null) { diff --git a/example/lib/rokt_layouts_screen.dart b/example/lib/rokt_layouts_screen.dart index 935a824..60c9713 100644 --- a/example/lib/rokt_layouts_screen.dart +++ b/example/lib/rokt_layouts_screen.dart @@ -192,7 +192,7 @@ class _RoktLayoutsScreenState extends State { .identify(identityRequest: identityRequest); widget.mpInstance?.rokt.selectShoppableAds( - identifier: 'MSDKShoppableAdsLayout', + identifier: 'StgRoktShoppableAds', attributes: _getShoppableAdsAttributes()); print('${_getPlatform()} Rokt selectShoppableAds called'); } catch (e) { diff --git a/example/pubspec.lock b/example/pubspec.lock index 9c22604..2f9aa2a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,26 +21,26 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" file: dependency: transitive description: @@ -99,34 +99,34 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: @@ -139,25 +139,25 @@ packages: dependency: transitive description: name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.17.0" mparticle_flutter_sdk: dependency: "direct main" description: path: ".." relative: true source: path - version: "1.1.1" + version: "2.0.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" platform: dependency: transitive description: @@ -191,18 +191,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -231,18 +231,18 @@ packages: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.7" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -260,5 +260,5 @@ packages: source: hosted version: "3.0.4" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.8.0-0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.lock b/pubspec.lock index feb96f5..43bd1fd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,34 +21,34 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -68,63 +68,63 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.2" matcher: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.17.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -137,18 +137,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -169,18 +169,18 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.7" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: @@ -190,5 +190,5 @@ packages: source: hosted version: "13.0.0" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.8.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" From f3c8eb6ac276e510f7551e61376a677f59cb18e8 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Fri, 8 May 2026 13:25:09 -0400 Subject: [PATCH 6/8] fix: restore android example min sdk to 21 Revert the example app minSdk configuration to 21 to keep release APK builds stable with core library desugaring in CI. --- example/android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 39f889c..c7b540d 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 flutter.minSdkVersion + minSdkVersion 21 targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName From 3205fccf7cc86cc99541e3c56f0a2c76021fad00 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Fri, 8 May 2026 14:00:28 -0400 Subject: [PATCH 7/8] docs: update rokt setup and migration guidance Document explicit Rokt event subscriptions and iOS payment extension setup for shoppable ads in both README and migration notes. --- MIGRATING.md | 4 ++-- README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/MIGRATING.md b/MIGRATING.md index 2a106bb..9e84e01 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -92,6 +92,6 @@ await MparticleFlutterSdk.getInstance().then((mp) => mp?.rokt.selectShoppableAds - **Android**: the method is exposed for API parity but is a no-op (logs a warning). - **Web**: not implemented — calls will throw `MissingPluginException`. -Events for a shoppable ads placement are delivered on the existing `MPRoktEvents` `EventChannel` once `selectShoppableAds` has been called. +Rokt event delivery now uses explicit subscription by identifier through `Rokt.events(...)`. Call `events(identifier, ...)` before `selectPlacements(...)` or `selectShoppableAds(...)` for that identifier. -The Rokt Apple Pay payment extension (for example `RoktStripePaymentExtension`) is **not** proxied through Dart. Integrators must register it directly from native Swift/Objective-C in the host app (for example `ios/Runner/AppDelegate.swift`), after `MParticle.sharedInstance().start(with:)`. See the [Apple SDK Rokt integration section](https://github.com/mParticle/mparticle-apple-sdk/blob/main/README.md#rokt-integration) for the exact snippet. +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:)`. diff --git a/README.md b/README.md index abbb86b..a8ac475 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,58 @@ You must first call `getInstance` on `MparticleFlutterSdk` before each method is MparticleFlutterSdk? mpInstance = await MparticleFlutterSdk.getInstance(); ``` +### Rokt + +`Rokt` is exposed under `mpInstance?.rokt` and supports: + +- `events(String identifier, void Function(dynamic event) onEvent)` +- `selectPlacements(...)` +- `selectShoppableAds(...)` (iOS implementation; Android no-op for parity; web unsupported) +- `purchaseFinalized(...)` (iOS) + +Subscribe to events for a placement identifier before selecting placements: + +```dart +mpInstance?.rokt.events('MSDKEmbeddedLayout', (event) { + print('Rokt event: $event'); +}); + +await mpInstance?.rokt.selectPlacements( + identifier: 'MSDKEmbeddedLayout', + attributes: {'email': 'user@example.com'}, +); +``` + +For iOS shoppable ads: + +```dart +mpInstance?.rokt.events('StgRoktShoppableAds', (event) { + print('Rokt shoppable event: $event'); +}); + +await mpInstance?.rokt.selectShoppableAds( + identifier: 'StgRoktShoppableAds', + attributes: {'email': 'user@example.com'}, +); +``` + +To enable iOS payment flows for shoppable ads, add the payment extension pod in your app `ios/Podfile` and register it natively after `MParticle.sharedInstance().start(with:)`: + +```ruby +pod 'RoktPaymentExtension' +``` + +```swift +import RoktPaymentExtension + +if let paymentExtension = RoktPaymentExtension( + applePayMerchantId: "merchant.com.example.app", + urlScheme: nil +) { + MParticle.sharedInstance().rokt.registerPaymentExtension(paymentExtension) +} +``` + ### Custom Events To log events, import mParticle `EventTypes` and `MPEvent` to write proper event logging calls: From aebfdadc24a2ae9543dae4b7938f10edcd4b0c93 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Fri, 8 May 2026 15:35:58 -0400 Subject: [PATCH 8/8] fix: deduplicate iOS rokt event subscriptions by identifier Prevent duplicate callback registration when events() is called repeatedly for the same placement identifier on iOS. --- ios/Classes/RoktEventHandler.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ios/Classes/RoktEventHandler.swift b/ios/Classes/RoktEventHandler.swift index 5d53de7..d423e1b 100644 --- a/ios/Classes/RoktEventHandler.swift +++ b/ios/Classes/RoktEventHandler.swift @@ -19,6 +19,7 @@ import RoktContracts class RoktEventHandler: NSObject, FlutterStreamHandler { private var eventListeners: [String: [FlutterEventSink]] = [:] + private var subscribedIdentifiers: Set = [] private let eventQueue = DispatchQueue(label: "com.mparticle.rokt.event.queue") private let EVENT_CHANNEL_NAME = "MPRoktEvents" @@ -59,6 +60,18 @@ class RoktEventHandler: NSObject, FlutterStreamHandler { func subscribeToEvents(identifier: String) { + let shouldSubscribe = eventQueue.sync { + if subscribedIdentifiers.contains(identifier) { + return false + } + subscribedIdentifiers.insert(identifier) + return true + } + + guard shouldSubscribe else { + return + } + MParticle.sharedInstance().rokt.events(identifier) { event in var params: [String: String] = [:]