From 370428c012a31d5ab56e0c05d133c1892f527324 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 15 Apr 2026 11:43:10 -0400 Subject: [PATCH 1/4] feat(rokt): add selectShoppableAds interface and iOS bridge Expose selectShoppableAds in the Flutter API with iOS native implementation, Android no-op parity, and platform tests while keeping pod dependencies minimal via transitive resolution. Made-with: Cursor --- .../MparticleFlutterSdkPlugin.kt | 6 ++++ .../SwiftMparticleFlutterSdkPlugin.swift | 22 +++++++++++++++ ios/mparticle_flutter_sdk.podspec | 4 +-- lib/mparticle_flutter_sdk.dart | 16 +++++++++++ lib/mparticle_flutter_sdk_web.dart | 12 ++++++++ test/mparticle_flutter_sdk_test.dart | 28 +++++++++++++++++++ 6 files changed, 85 insertions(+), 3 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 55e9724..bdaf820 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 result.success(true) } "roktSelectPlacements" -> this.roktSelectPlacements(call, result) + "roktSelectShoppableAds" -> this.roktSelectShoppableAds(call, result) "roktPurchaseFinalized" -> this.roktPurchaseFinalized(call, result) else -> { result.notImplemented() @@ -806,6 +807,11 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware } } + private fun roktSelectShoppableAds(call: MethodCall, result: Result) { + // Parity with RN bridge: Android API is exposed but not implemented yet. + result.success(true) + } + private fun String.toColorMode(): RoktConfig.ColorMode = when (this) { "dark" -> RoktConfig.ColorMode.DARK diff --git a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift index 72f8048..02efbc1 100644 --- a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift @@ -561,6 +561,28 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin { print("Incorrect argument for \(call.method) iOS method") result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing placementId or catalogItemId or success", details: nil)) } + case "roktSelectShoppableAds": + if let callArguments = call.arguments as? [String: Any], + let placementId = callArguments["placementId"] as? String { + let attributes = callArguments["attributes"] as? [String: String] ?? [:] + var roktConfig: RoktConfig? + if let configMap = callArguments["config"] as? [String: Any] { + roktConfig = buildRoktConfig(configMap: configMap) + } + + roktEventHandler.subscribeToEvents(identifier: placementId) + MParticle.sharedInstance().rokt.selectShoppableAds( + placementId, + attributes: attributes, + config: roktConfig + ) { _ in + // Event propagation is handled by subscribeToEvents(identifier:) + } + result(true) + } else { + print("Incorrect argument for \(call.method) iOS method") + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing placementId", details: nil)) + } default: print("mParticle flutter SDK for iOS does not support \(call.method)") } diff --git a/ios/mparticle_flutter_sdk.podspec b/ios/mparticle_flutter_sdk.podspec index b113d0d..a0d5366 100644 --- a/ios/mparticle_flutter_sdk.podspec +++ b/ios/mparticle_flutter_sdk.podspec @@ -15,10 +15,8 @@ mParticle Flutter Wrapper s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'Flutter' - # SDK 9.0 split requires both umbrella and ObjC pods. + # SDK 9.0 umbrella pod pulls required transitive dependencies. s.dependency 'mParticle-Apple-SDK', '~> 9.0' - s.dependency 'mParticle-Apple-SDK-ObjC', '~> 9.0' - s.dependency 'RoktContracts', '~> 0.1' s.platform = :ios, '15.6' # Flutter.framework does not contain a i386 slice. diff --git a/lib/mparticle_flutter_sdk.dart b/lib/mparticle_flutter_sdk.dart index 39b51f3..34434df 100644 --- a/lib/mparticle_flutter_sdk.dart +++ b/lib/mparticle_flutter_sdk.dart @@ -331,6 +331,22 @@ class Rokt { return await _channel.invokeMethod('roktSelectPlacements', params); } + /// Selects shoppable ads with a [identifier], optional [attributes], and optional [roktConfig]. + /// + /// This method currently has native implementation on iOS. + /// Android keeps a no-op bridge for cross-platform API compatibility. + Future selectShoppableAds({ + required String identifier, + Map? attributes, + RoktConfig? roktConfig, + }) async { + return await _channel.invokeMethod('roktSelectShoppableAds', { + 'placementId': identifier, + 'attributes': attributes, + 'config': _roktConfigToMap(config: roktConfig), + }); + } + /// Notifies Rokt that a purchase has been finalized /// /// Use this method to inform Rokt that a purchase has been completed or failed diff --git a/lib/mparticle_flutter_sdk_web.dart b/lib/mparticle_flutter_sdk_web.dart index 8a8c50c..e2258fd 100644 --- a/lib/mparticle_flutter_sdk_web.dart +++ b/lib/mparticle_flutter_sdk_web.dart @@ -536,6 +536,18 @@ class MparticleFlutterSdkWeb { }) ]); break; + case 'roktSelectShoppableAds': + final mpRokt = JsObject.fromBrowserObject(context['mParticle']['Rokt']); + final placementId = call.arguments['placementId']; + final attributes = call.arguments['attributes'] ?? {}; + + mpRokt.callMethod('selectShoppableAds', [ + JsObject.jsify({ + 'identifier': placementId, + 'attributes': attributes + }) + ]); + break; default: throw PlatformException( code: 'Unimplemented', diff --git a/test/mparticle_flutter_sdk_test.dart b/test/mparticle_flutter_sdk_test.dart index 39a06f0..ebb3bbc 100644 --- a/test/mparticle_flutter_sdk_test.dart +++ b/test/mparticle_flutter_sdk_test.dart @@ -502,5 +502,33 @@ void main() { 'success': true, })); }); + + test('rokt select shoppable ads', () async { + final roktConfig = RoktConfig( + colorMode: ColorMode.dark, + cacheConfig: CacheConfig( + cacheDurationInSeconds: 100, + cacheAttributes: {'key1': 'value1'})); + + await mp.rokt.selectShoppableAds( + identifier: 'placement1', + attributes: {'attr1': 'val1'}, + roktConfig: roktConfig, + ); + + expect( + methodCall, + isMethodCall('roktSelectShoppableAds', arguments: { + 'placementId': 'placement1', + 'attributes': {'attr1': 'val1'}, + 'config': { + 'colorMode': 'dark', + 'cacheConfig': { + 'cacheDurationInSeconds': 100, + 'cacheAttributes': {'key1': 'value1'} + } + }, + })); + }); }); } From ea9766c0c566719514de0026131da970db424519 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 15 Apr 2026 13:18:14 -0400 Subject: [PATCH 2/4] fix(rokt): limit selectShoppableAds support to iOS Remove the web selectShoppableAds bridge call and document platform behavior so the API remains explicitly iOS-only for now. Made-with: Cursor --- lib/mparticle_flutter_sdk.dart | 6 ++++-- lib/mparticle_flutter_sdk_web.dart | 12 ------------ 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/lib/mparticle_flutter_sdk.dart b/lib/mparticle_flutter_sdk.dart index 34434df..03e3ed1 100644 --- a/lib/mparticle_flutter_sdk.dart +++ b/lib/mparticle_flutter_sdk.dart @@ -333,8 +333,10 @@ class Rokt { /// Selects shoppable ads with a [identifier], optional [attributes], and optional [roktConfig]. /// - /// This method currently has native implementation on iOS. - /// Android keeps a no-op bridge for cross-platform API compatibility. + /// This method is currently implemented only on iOS. + /// + /// Android keeps a no-op bridge for API compatibility, and web does not + /// implement this method yet. Future selectShoppableAds({ required String identifier, Map? attributes, diff --git a/lib/mparticle_flutter_sdk_web.dart b/lib/mparticle_flutter_sdk_web.dart index e2258fd..8a8c50c 100644 --- a/lib/mparticle_flutter_sdk_web.dart +++ b/lib/mparticle_flutter_sdk_web.dart @@ -536,18 +536,6 @@ class MparticleFlutterSdkWeb { }) ]); break; - case 'roktSelectShoppableAds': - final mpRokt = JsObject.fromBrowserObject(context['mParticle']['Rokt']); - final placementId = call.arguments['placementId']; - final attributes = call.arguments['attributes'] ?? {}; - - mpRokt.callMethod('selectShoppableAds', [ - JsObject.jsify({ - 'identifier': placementId, - 'attributes': attributes - }) - ]); - break; default: throw PlatformException( code: 'Unimplemented', From 2977de7c6f6aabaf52b2200317d50681c450f7e4 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 15 Apr 2026 14:02:13 -0400 Subject: [PATCH 3/4] update identifier --- ios/Classes/SwiftMparticleFlutterSdkPlugin.swift | 8 ++++---- lib/mparticle_flutter_sdk.dart | 2 +- test/mparticle_flutter_sdk_test.dart | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift index 02efbc1..576cc7b 100644 --- a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift @@ -563,16 +563,16 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin { } case "roktSelectShoppableAds": if let callArguments = call.arguments as? [String: Any], - let placementId = callArguments["placementId"] as? String { + let identifier = callArguments["identifier"] as? String { let attributes = callArguments["attributes"] as? [String: String] ?? [:] var roktConfig: RoktConfig? if let configMap = callArguments["config"] as? [String: Any] { roktConfig = buildRoktConfig(configMap: configMap) } - roktEventHandler.subscribeToEvents(identifier: placementId) + roktEventHandler.subscribeToEvents(identifier: identifier) MParticle.sharedInstance().rokt.selectShoppableAds( - placementId, + identifier, attributes: attributes, config: roktConfig ) { _ in @@ -581,7 +581,7 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin { result(true) } else { print("Incorrect argument for \(call.method) iOS method") - result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing placementId", details: nil)) + result(FlutterError(code: "INVALID_ARGUMENTS", message: "Missing identifier", details: nil)) } default: print("mParticle flutter SDK for iOS does not support \(call.method)") diff --git a/lib/mparticle_flutter_sdk.dart b/lib/mparticle_flutter_sdk.dart index 03e3ed1..b629ca0 100644 --- a/lib/mparticle_flutter_sdk.dart +++ b/lib/mparticle_flutter_sdk.dart @@ -343,7 +343,7 @@ class Rokt { RoktConfig? roktConfig, }) async { return await _channel.invokeMethod('roktSelectShoppableAds', { - 'placementId': identifier, + 'identifier': identifier, 'attributes': attributes, 'config': _roktConfigToMap(config: roktConfig), }); diff --git a/test/mparticle_flutter_sdk_test.dart b/test/mparticle_flutter_sdk_test.dart index ebb3bbc..e27c27f 100644 --- a/test/mparticle_flutter_sdk_test.dart +++ b/test/mparticle_flutter_sdk_test.dart @@ -511,7 +511,7 @@ void main() { cacheAttributes: {'key1': 'value1'})); await mp.rokt.selectShoppableAds( - identifier: 'placement1', + identifier: 'identifier1', attributes: {'attr1': 'val1'}, roktConfig: roktConfig, ); @@ -519,7 +519,7 @@ void main() { expect( methodCall, isMethodCall('roktSelectShoppableAds', arguments: { - 'placementId': 'placement1', + 'identifier': 'identifier1', 'attributes': {'attr1': 'val1'}, 'config': { 'colorMode': 'dark', From 80e43bc6a37d9867d7cb23bb267d29376c87e454 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Thu, 16 Apr 2026 15:50:24 -0400 Subject: [PATCH 4/4] chore(rokt): address PR feedback for selectShoppableAds - Android: log a warning in roktSelectShoppableAds to match RN bridge behavior - iOS: pass nil for onEvent since events are propagated via subscribeToEvents --- .../mparticle_flutter_sdk/MparticleFlutterSdkPlugin.kt | 2 ++ ios/Classes/SwiftMparticleFlutterSdkPlugin.swift | 7 +++---- 2 files changed, 5 insertions(+), 4 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 bdaf820..8e3aa6e 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 @@ -26,6 +26,7 @@ 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 @@ -809,6 +810,7 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware 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) } diff --git a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift index 576cc7b..a02658b 100644 --- a/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift +++ b/ios/Classes/SwiftMparticleFlutterSdkPlugin.swift @@ -574,10 +574,9 @@ public class SwiftMparticleFlutterSdkPlugin: NSObject, FlutterPlugin { MParticle.sharedInstance().rokt.selectShoppableAds( identifier, attributes: attributes, - config: roktConfig - ) { _ in - // Event propagation is handled by subscribeToEvents(identifier:) - } + config: roktConfig, + onEvent: nil + ) result(true) } else { print("Incorrect argument for \(call.method) iOS method")