feat(android): support mParticle Android SDK 6#358
Conversation
Update the Android bridge to compile against mParticle Android SDK 6 and the Rokt kit APIs while keeping the existing React Native Rokt JavaScript surface. The Expo config plugin and sample now use bounded 6.x Android kit ranges, and migration docs describe the SDK and build requirements for app developers.
PR SummaryHigh Risk Overview Bridge — Rokt native — Calls use Reviewed by Cursor Bugbot for commit cc78cff. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
Updates the React Native mParticle Android integration to support mParticle Android SDK 6, including the updated Rokt kit APIs, while keeping the existing JavaScript Rokt API surface. It also adjusts dependency ranges and documentation to reflect the new Android SDK/kit requirements, and guards Android Rokt module/view registration when the kit is not present.
Changes:
- Bump Android core/kit dependency ranges to the supported 6.x line across the library, config plugin, and sample app.
- Update Android bridge implementation for SDK 6 (Rokt API changes; typed user attribute listener).
- Gate Android Rokt TurboModule + view manager registration when the Android Rokt kit is not on the classpath; keep iOS behavior unchanged.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| sample/README.md | Updates sample app guidance for Android SDK 6 Rokt dependencies/requirements. |
| sample/android/app/build.gradle | Bumps sample Android dependencies to mParticle 6.x ranges. |
| README.md | Updates public docs for Android Rokt requirements with SDK 6. |
| plugin/src/withMParticleAndroid.ts | Updates Expo config plugin kit injection to 6.x bounded ranges. |
| MIGRATING.md | Adds migration notes for Android SDK 6 / Rokt requirements. |
| js/rokt/rokt.ts | Makes Android Rokt module resolution conditional and throws when unavailable. |
| ExpoTestApp/README.md | Updates Expo test app docs/examples for Android SDK 6 Rokt requirements and dependency range. |
| android/src/oldarch/java/com/mparticle/react/rokt/RoktLayoutViewManager.kt | Updates Rokt embedded view import for SDK 6 kit package. |
| android/src/oldarch/java/com/mparticle/react/rokt/MPRoktModule.kt | Updates old-arch Rokt module to SDK 6 rokt API usage. |
| android/src/newarch/java/com/mparticle/react/rokt/RoktLayoutViewManager.kt | Updates new-arch view manager to SDK 6 kit package import and formatting. |
| android/src/newarch/java/com/mparticle/react/rokt/MPRoktModule.kt | Updates new-arch Rokt module to SDK 6 rokt API usage. |
| android/src/main/java/com/mparticle/react/rokt/RoktLayoutViewManagerImpl.kt | Updates imports for SDK 6 kit callback types and removes obsolete callback handling. |
| android/src/main/java/com/mparticle/react/rokt/MPRoktModuleImpl.kt | Reworks event/callback bridging to SDK 6 Rokt events and config types. |
| android/src/main/java/com/mparticle/react/MParticlePackage.kt | Guards Android Rokt module and view registration when kit is absent. |
| android/src/main/java/com/mparticle/react/MParticleModule.kt | Migrates user attribute retrieval to SDK 6 typed attribute listener. |
| android/build.gradle | Moves library core dependency to 6.x range and adds compileOnly Rokt kit + test deps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…TEventEmitter The Rokt embedded layout view manager emitted its height-changed event through `context.getJSModule(RCTEventEmitter).receiveEvent(...)`. Under the New Architecture bridgeless runtime (default on RN >= 0.76, the wrapper's floor) this logs an `IllegalArgumentException` SoftException on every height change and stops working entirely once the Fabric interop layer is disabled — which would drop the height event and leave embedded placements mis-sized. Replace it with `UIManagerHelper.getEventDispatcherForReactTag` + an `Event` subclass overriding `getEventData()`. This is the RN-recommended path and works on both Paper and Fabric across all supported RN versions, from a single `main/` implementation (no arch/version branching). Also remove the dead margin path (`changeMargin`, `EVENT_MARGIN_CHANGED`, `onLayoutMarginChanged`, `MarginChangedEvent`): mParticle Android SDK 6's `RoktLayoutDimensionCallBack` only exposes `onHeightChanged`, so nothing ever fired the margin event. iOS never emitted it either (height flows via the separate RoktEventManager emitter), so dropping it from the shared codegen spec is safe on both platforms. Verified: sample app builds, embedded placement renders to interactive at the correct height with no SoftException in logcat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Under the New Architecture, `RoktLayoutViewManager` implements the codegen-generated `RoktNativeLayoutManagerInterface` but never returned a delegate from `getDelegate()`. On every view creation Fabric calls `getOrCreateViewManagerDelegate()`, the base ViewManager returns null and logs `ReactNoCrashSoftException: ViewManager using codegen must override getDelegate method (name: RoktNativeLayout)`, and the `placeholderName` prop is not routed through the generated delegate. Instantiate `RoktNativeLayoutManagerDelegate` and return it from `getDelegate()`. The delegate's `setProperty` now owns prop dispatch, so the redundant `@ReactProp` annotation is removed. Explicit generic type arguments are supplied because Kotlin cannot infer the delegate's intersection-bounded type parameter. newarch source set only; the Paper `ViewGroupManager` path uses no codegen delegate and is unaffected. Verified: sample app builds, embedded placement renders to interactive with no getDelegate SoftException. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Under the New Architecture, `MPRoktModule.selectPlacements` invoked the SDK's
`rokt.selectPlacements(...)` on the TurboModule thread. mParticle Android SDK
6's `RoktKit.selectPlacements` clears prior placeholder content with
`ViewGroup.removeAllViews()`, which detaches the now Compose-based Rokt widget
and triggers `LifecycleRegistry.removeObserver` — hard-enforced to the main
thread. Showing an embedded placement then another placement crashed with
`IllegalStateException: Method removeObserver must be called on the main thread`.
The bug was latent before SDK 6 (the old View-based widget tolerated the
off-thread detach); the Compose migration turned it into a deterministic crash.
Resolve placeholder views and call the SDK together inside a single
`UiThreadUtil.runOnUiThread {}`, dropping the CountDownLatch (which also removed
a blocking cross-thread wait on the module thread). Guard `close()` the same
way, since `rokt.close()` detaches overlay Compose views. This matches the
oldarch path (`UIManager.addUIBlock`) and iOS (uiManager methodQueue), both of
which already dispatch on the main thread and were unaffected.
Verified: embedded → overlay no longer crashes and both render to interactive;
ROKT CLOSE dismisses the overlay (PlacementClosed) with no exception.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… managers Join single-expression bodies onto their signatures (getEventData, createViewInstance) and wrap the multi-parameter setPlaceholderName signature, matching the repo's existing Kotlin style and clearing the standard:function-signature violations from ktlintMainSourceSetCheck. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9204e57. Configure here.
These three methods are typed to return a Promise but called getMPRokt() synchronously, so a missing-native-module error threw synchronously instead of rejecting the returned Promise — inconsistent with the type signature and with the sibling methods (selectPlacements, selectShoppableAds, purchaseFinalized), which are already async. A caller using `.catch()` would miss the error. Mark them async so the getMPRokt() throw surfaces as a rejected Promise. Return types are unchanged, so this is non-breaking; native promise resolution is unaffected. Addresses copilot PR review comments on js/rokt/rokt.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
isRoktKitAvailable() ran a reflective Class.forName on each of its four call sites during module and view-manager registration at startup. The kit's presence on the classpath is fixed for the app's lifetime, so convert it to a `by lazy` val that performs the reflection once. Addresses copilot PR review comment on MParticlePackage.kt. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
State that both com.mparticle:android-core and android-rokt-kit are pinned to [6.0.0, 7.0) (removes the ambiguity flagged in review) and drop the Android CNAME mention. Addresses copilot PR review comment on sample/README.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrap the Android dependency paragraph so a line no longer begins with "36." (which prettier/markdown would treat as an ordered-list item), satisfying the trunk prettier check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Summary
Support mParticle Android SDK 6 for the React Native Android bridge and Rokt integrations. The bridge now uses the SDK 6 user attribute and Rokt kit APIs while keeping the existing JavaScript Rokt API surface. Android Rokt native module and view registration is guarded when the Android Rokt kit is not present, and iOS native module loading keeps the existing fail-fast behavior.
The Expo config plugin and sample Android app now use bounded 6.x Android kit dependency ranges. The migration docs describe the Android SDK 6 Rokt requirements for SDK consumers and avoid release-generated changelog or version updates.
Testing Plan
yarn buildyarn build:pluginyarn testcd android && ./gradlew test lint ktlintChecktrunk check --ciyarn dev:packcd sample && yarn install --no-lockfile --ignore-scripts(with Node >=22.11.0)cd sample/android && ./gradlew assembleDebug