Skip to content

Commit fe8c1ab

Browse files
authored
feat: update rokt example validation flow (#72)
* 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. * 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. * fix: remove implicit shoppable ads event subscription Stop auto-subscribing to Rokt events inside selectShoppableAds so event delivery consistently relies on explicit subscription APIs. * 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. * 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. * 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. * 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. * fix: deduplicate iOS rokt event subscriptions by identifier Prevent duplicate callback registration when events() is called repeatedly for the same placement identifier on iOS.
1 parent 6179f02 commit fe8c1ab

13 files changed

Lines changed: 226 additions & 82 deletions

File tree

MIGRATING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,6 @@ await MparticleFlutterSdk.getInstance().then((mp) => mp?.rokt.selectShoppableAds
9292
- **Android**: the method is exposed for API parity but is a no-op (logs a warning).
9393
- **Web**: not implemented — calls will throw `MissingPluginException`.
9494

95-
Events for a shoppable ads placement are delivered on the existing `MPRoktEvents` `EventChannel` once `selectShoppableAds` has been called.
95+
Rokt event delivery now uses explicit subscription by identifier through `Rokt.events(...)`. Call `events(identifier, ...)` before `selectPlacements(...)` or `selectShoppableAds(...)` for that identifier.
9696

97-
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.
97+
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:)`.

README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ To install mParticle on an iOS platform:
117117
2. Install the SDK using CocoaPods:
118118

119119
```bash
120-
$ # Update your Podfile to depend on 'mParticle-Apple-SDK' version 8.5.0 or later
120+
$ # Update your Podfile to depend on 'mParticle-Apple-SDK' version 9.1.0 or later
121121
$ pod install
122122
```
123123

@@ -256,6 +256,58 @@ You must first call `getInstance` on `MparticleFlutterSdk` before each method is
256256
MparticleFlutterSdk? mpInstance = await MparticleFlutterSdk.getInstance();
257257
```
258258

259+
### Rokt
260+
261+
`Rokt` is exposed under `mpInstance?.rokt` and supports:
262+
263+
- `events(String identifier, void Function(dynamic event) onEvent)`
264+
- `selectPlacements(...)`
265+
- `selectShoppableAds(...)` (iOS implementation; Android no-op for parity; web unsupported)
266+
- `purchaseFinalized(...)` (iOS)
267+
268+
Subscribe to events for a placement identifier before selecting placements:
269+
270+
```dart
271+
mpInstance?.rokt.events('MSDKEmbeddedLayout', (event) {
272+
print('Rokt event: $event');
273+
});
274+
275+
await mpInstance?.rokt.selectPlacements(
276+
identifier: 'MSDKEmbeddedLayout',
277+
attributes: {'email': 'user@example.com'},
278+
);
279+
```
280+
281+
For iOS shoppable ads:
282+
283+
```dart
284+
mpInstance?.rokt.events('StgRoktShoppableAds', (event) {
285+
print('Rokt shoppable event: $event');
286+
});
287+
288+
await mpInstance?.rokt.selectShoppableAds(
289+
identifier: 'StgRoktShoppableAds',
290+
attributes: {'email': 'user@example.com'},
291+
);
292+
```
293+
294+
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:)`:
295+
296+
```ruby
297+
pod 'RoktPaymentExtension'
298+
```
299+
300+
```swift
301+
import RoktPaymentExtension
302+
303+
if let paymentExtension = RoktPaymentExtension(
304+
applePayMerchantId: "merchant.com.example.app",
305+
urlScheme: nil
306+
) {
307+
MParticle.sharedInstance().rokt.registerPaymentExtension(paymentExtension)
308+
}
309+
```
310+
259311
### Custom Events
260312

261313
To log events, import mParticle `EventTypes` and `MPEvent` to write proper event logging calls:

android/src/main/kotlin/com/mparticle/mparticle_flutter_sdk/MparticleFlutterSdkPlugin.kt

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware
235235
setSdkVersion()
236236
result.success(true)
237237
}
238+
"roktSubscribeToEvents" -> this.roktSubscribeToEvents(call, result)
238239
"roktSelectPlacements" -> this.roktSelectPlacements(call, result)
239240
"roktSelectShoppableAds" -> this.roktSelectShoppableAds(call, result)
240241
"roktPurchaseFinalized" -> this.roktPurchaseFinalized(call, result)
@@ -756,16 +757,6 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware
756757
}
757758

758759
MParticle.getInstance()?.let { instance ->
759-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
760-
activity?.let {
761-
roktEventHandler?.subscribeToEvents(
762-
events = instance.Rokt().events(placementId),
763-
activity = it,
764-
identifier = placementId,
765-
)
766-
}
767-
}
768-
769760
instance.Rokt().selectPlacements(placementId, stringAttributes, null, placeHolders.takeIf { it.isNotEmpty() }, customFonts, config)
770761
result.success(true)
771762
} ?: result.error(TAG, "No mParticle instance exists", null)
@@ -774,6 +765,27 @@ class MparticleFlutterSdkPlugin: FlutterPlugin, MethodCallHandler, ActivityAware
774765
}
775766
}
776767

768+
private fun roktSubscribeToEvents(call: MethodCall, result: Result) {
769+
val identifier = call.argument<String>("identifier")
770+
if (identifier.isNullOrBlank()) {
771+
result.error(TAG, "Missing identifier", null)
772+
return
773+
}
774+
775+
MParticle.getInstance()?.let { instance ->
776+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
777+
activity?.let { currentActivity ->
778+
roktEventHandler?.subscribeToEvents(
779+
events = instance.Rokt().events(identifier),
780+
activity = currentActivity,
781+
identifier = identifier,
782+
)
783+
}
784+
}
785+
result.success(true)
786+
} ?: result.error(TAG, "No mParticle instance exists", null)
787+
}
788+
777789
private fun buildRoktConfig(configMap: Map<String, Any>): RoktConfig {
778790
val builder = RoktConfig.Builder()
779791
(configMap["colorMode"] as? String)?.let {

example/ios/Podfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ target 'Runner' do
3131
use_frameworks!
3232
use_modular_headers!
3333

34-
pod 'mParticle-Apple-SDK'
3534
pod 'mParticle-Rokt'
35+
pod 'RoktPaymentExtension'
3636

3737
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
3838
end

example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
buildConfiguration = "Debug"
2727
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
2930
shouldUseLaunchSchemeArgsEnv = "YES">
3031
<MacroExpansion>
3132
<BuildableReference
@@ -43,11 +44,13 @@
4344
buildConfiguration = "Debug"
4445
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
4546
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
47+
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
4648
launchStyle = "0"
4749
useCustomWorkingDirectory = "NO"
4850
ignoresPersistentStateOnLaunch = "NO"
4951
debugDocumentVersioning = "YES"
5052
debugServiceExtension = "internal"
53+
enableGPUValidationMode = "1"
5154
allowLocationSimulation = "YES">
5255
<BuildableProductRunnable
5356
runnableDebuggingMode = "0">

example/lib/main.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ class _MyAppState extends State<MyApp> {
6565
// Platform messages are asynchronous, so we initialize in an async method.
6666
Future<void> initMparticle() async {
6767
mpInstance = await MparticleFlutterSdk.getInstance();
68+
mpInstance?.rokt.events('StgRoktShoppableAds', (event) {
69+
print("Rokt event: $event");
70+
});
6871
if (mpInstance != null) {
6972
setState(() {
7073
_isInitialized = true;

example/lib/rokt_layouts_screen.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,30 @@ class _RoktLayoutsScreenState extends State<RoktLayoutsScreen> {
4848
return {};
4949
}
5050

51+
Map<String, String> _getShoppableAdsAttributes() {
52+
return {
53+
'country': 'US',
54+
'shippingstate': 'NY',
55+
'shippingzipcode': '10001',
56+
'firstname': 'Jenny',
57+
'stripeApplePayAvailable': 'true',
58+
'last4digits': '4444',
59+
'shippingaddress1': '123 Main St',
60+
'colormode': 'LIGHT',
61+
'billingzipcode': '07762',
62+
'paymenttype': 'ApplePay',
63+
'shippingcountry': 'US',
64+
'sandbox': 'true',
65+
'shippingaddress2': 'Apt 4B',
66+
'confirmationref': 'ORD-12345',
67+
'shippingcity': 'New York',
68+
'newToApplePay': 'false',
69+
'applePayCapabilities': 'true',
70+
'lastname': 'Smith',
71+
'email': 'jenny.smith@example.com',
72+
};
73+
}
74+
5175
String _getPlatform() {
5276
if (kIsWeb) {
5377
return 'Web';
@@ -151,6 +175,30 @@ class _RoktLayoutsScreenState extends State<RoktLayoutsScreen> {
151175
print('Error calling Rokt selectPlacements: $e');
152176
}
153177
}),
178+
const SizedBox(height: 20),
179+
buildButton('Select Shoppable Ads (iOS)', () async {
180+
if (!Platform.isIOS) {
181+
print('${_getPlatform()} SelectShoppableAds is a no-op');
182+
return;
183+
}
184+
185+
var identityRequest = MparticleFlutterSdk.identityRequest;
186+
identityRequest.setIdentity(
187+
identityType: IdentityType.CustomerId,
188+
value: _getIdentityValue());
189+
190+
try {
191+
await widget.mpInstance?.identity
192+
.identify(identityRequest: identityRequest);
193+
194+
widget.mpInstance?.rokt.selectShoppableAds(
195+
identifier: 'StgRoktShoppableAds',
196+
attributes: _getShoppableAdsAttributes());
197+
print('${_getPlatform()} Rokt selectShoppableAds called');
198+
} catch (e) {
199+
print('Error calling Rokt selectShoppableAds: $e');
200+
}
201+
}),
154202
Center(
155203
child: Text("Location1", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
156204
),

0 commit comments

Comments
 (0)