From 8697332f591e557183cb5202f99bb60701f2fcd8 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Tue, 12 May 2026 13:04:19 -0400 Subject: [PATCH 01/23] feat: move Rokt object creation into rokt kit Stop instantiating and storing Rokt inside android-core MParticle, and create it from rokt-kit extensions instead to start decoupling Rokt object ownership from the core SDK. --- .../main/java/com/mparticle/MParticle.java | 7 ----- .../mparticle/kits/MParticleRoktExtensions.kt | 28 +++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt diff --git a/android-core/src/main/java/com/mparticle/MParticle.java b/android-core/src/main/java/com/mparticle/MParticle.java index eedd06cbc..a74cf7260 100644 --- a/android-core/src/main/java/com/mparticle/MParticle.java +++ b/android-core/src/main/java/com/mparticle/MParticle.java @@ -110,7 +110,6 @@ public class MParticle { protected boolean locationTrackingEnabled = false; @NonNull protected Internal mInternal = new Internal(); - protected Rokt rokt; private IdentityStateListener mDeferredModifyPushRegistrationListener; @NonNull private WrapperSdkVersion wrapperSdkVersion = new WrapperSdkVersion(WrapperSdk.WrapperNone, null); @@ -190,7 +189,6 @@ private static MParticle getInstance(@NonNull Context context, @NonNull MParticl instance = new MParticle(options); instance.mKitManager = new KitFrameworkWrapper(options.getContext(), instance.mMessageManager, instance.Internal().getConfigManager(), instance.Internal().getAppStateManager(), options); instance.mIdentityApi = new IdentityApi(options.getContext(), instance.mInternal.getAppStateManager(), instance.mMessageManager, instance.mConfigManager, instance.mKitManager, options.getOperatingSystem()); - instance.rokt = new Rokt(instance.mConfigManager, instance.mKitManager); // Check if we've switched workspaces on startup UploadSettings lastUploadSettings = instance.mConfigManager.getLastUploadSettings(); @@ -1128,11 +1126,6 @@ public void logNotificationOpened(@NonNull Intent intent) { public Internal Internal() { return mInternal; } - @NonNull - public Rokt Rokt() { - return rokt; - } - void refreshConfiguration() { Logger.debug("Refreshing configuration..."); mMessageManager.refreshConfiguration(); diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt new file mode 100644 index 000000000..401cc92d0 --- /dev/null +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt @@ -0,0 +1,28 @@ +package com.mparticle.rokt + +import com.mparticle.MParticle +import com.mparticle.Rokt + +/** + * Kotlin-friendly accessor for the legacy Rokt API object. + */ +fun MParticle.Rokt(): Rokt = createRokt(this) + +/** + * Java-friendly accessors for the legacy Rokt API object. + */ +object MParticleRokt { + @JvmStatic + fun Rokt(mParticle: MParticle?): Rokt? = mParticle?.let { createRokt(it) } + + @JvmStatic + fun Rokt(): Rokt? = MParticle.getInstance()?.let { createRokt(it) } +} + +private fun createRokt(mParticle: MParticle): Rokt { + val configManager = mParticle.Internal().configManager + val kitManager = mParticle.Internal().kitManager + val constructor = Rokt::class.java.getDeclaredConstructor(configManager::class.java, kitManager::class.java) + constructor.isAccessible = true + return constructor.newInstance(configManager, kitManager) +} From 6f05c414e58d3a2709e6746710a6cb8ac9f5e790 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Tue, 12 May 2026 13:32:45 -0400 Subject: [PATCH 02/23] feat: move Rokt facade and tests into rokt kit Relocate the Rokt facade class and its unit tests from android-core to the rokt kit so Rokt-specific API ownership continues shifting out of core while preserving current behavior and test coverage. --- kits/rokt/rokt/build.gradle | 5 +++ .../src/main/kotlin/com/mparticle/Rokt.kt | 36 ++++++++++++------- .../mparticle/kits/MParticleRoktExtensions.kt | 4 +-- .../src/test/kotlin/com/mparticle/RoktTest.kt | 35 +++++++++--------- 4 files changed, 49 insertions(+), 31 deletions(-) rename {android-core => kits/rokt/rokt}/src/main/kotlin/com/mparticle/Rokt.kt (79%) rename {android-core => kits/rokt/rokt}/src/test/kotlin/com/mparticle/RoktTest.kt (90%) diff --git a/kits/rokt/rokt/build.gradle b/kits/rokt/rokt/build.gradle index 4d49a2422..bf6fa5a84 100644 --- a/kits/rokt/rokt/build.gradle +++ b/kits/rokt/rokt/build.gradle @@ -80,6 +80,11 @@ dependencies { testImplementation files('libs/java-json.jar') testImplementation 'com.squareup.assertj:assertj-android:1.2.0' testImplementation ("io.mockk:mockk:1.13.4") + testImplementation 'junit:junit:4.13.2' + testImplementation 'org.powermock:powermock-module-junit4:2.0.7' + testImplementation 'org.powermock:powermock-api-mockito2:2.0.2' + testImplementation 'org.powermock:powermock-core:2.0.7' + testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' compileOnly 'androidx.compose.ui:ui' compileOnly 'androidx.compose.material:material' diff --git a/android-core/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt similarity index 79% rename from android-core/src/main/kotlin/com/mparticle/Rokt.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt index e531b7f44..496c2ad84 100644 --- a/android-core/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt @@ -1,10 +1,8 @@ package com.mparticle import android.graphics.Typeface -import com.mparticle.internal.ConfigManager import com.mparticle.internal.KitManager import com.mparticle.internal.Logger -import com.mparticle.internal.listeners.ApiClass import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView @@ -12,8 +10,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import java.lang.ref.WeakReference -@ApiClass -class Rokt internal constructor(private val mConfigManager: ConfigManager, private val mKitManager: KitManager) { +class Rokt internal constructor(private val mConfigManager: Any, private val mKitManager: KitManager) { /** * Display a Rokt placement with the specified parameters. @@ -34,10 +31,18 @@ class Rokt internal constructor(private val mConfigManager: ConfigManager, priva fontTypefaces: Map>? = null, config: RoktConfig? = null, ) { - if (mConfigManager.isEnabled) { + if (isEnabled()) { val roktApi = mKitManager.roktKitApi if (roktApi != null) { - roktApi.selectPlacements(identifier, HashMap(attributes), callbacks, embeddedViews, fontTypefaces, config, buildPlacementOptions()) + roktApi.selectPlacements( + identifier, + HashMap(attributes), + callbacks, + embeddedViews, + fontTypefaces, + config, + buildPlacementOptions(), + ) } else { Logger.warning("Rokt Kit is not available. Make sure the Rokt Kit is included in your app.") } @@ -50,7 +55,7 @@ class Rokt internal constructor(private val mConfigManager: ConfigManager, priva * @param identifier The placement identifier to listen for events * @return A Flow emitting RoktEvent objects */ - fun events(identifier: String): Flow = if (mConfigManager.isEnabled) { + fun events(identifier: String): Flow = if (isEnabled()) { mKitManager.roktKitApi?.events(identifier) ?: flowOf() } else { flowOf() @@ -64,7 +69,7 @@ class Rokt internal constructor(private val mConfigManager: ConfigManager, priva * @param status Whether the purchase was successful */ fun purchaseFinalized(placementId: String, catalogItemId: String, status: Boolean) { - if (mConfigManager.isEnabled) { + if (isEnabled()) { mKitManager.roktKitApi?.purchaseFinalized(placementId, catalogItemId, status) } } @@ -73,7 +78,7 @@ class Rokt internal constructor(private val mConfigManager: ConfigManager, priva * Close any active Rokt placements. */ fun close() { - if (mConfigManager.isEnabled) { + if (isEnabled()) { mKitManager.roktKitApi?.close() } } @@ -89,7 +94,7 @@ class Rokt internal constructor(private val mConfigManager: ConfigManager, priva * @param sessionId The session id to be set. Must be a non-empty string. */ fun setSessionId(sessionId: String) { - if (mConfigManager.isEnabled) { + if (isEnabled()) { mKitManager.roktKitApi?.setSessionId(sessionId) } } @@ -99,7 +104,7 @@ class Rokt internal constructor(private val mConfigManager: ConfigManager, priva * * @return The session id or null if no session is present or SDK is not initialized. */ - fun getSessionId(): String? = if (mConfigManager.isEnabled) { + fun getSessionId(): String? = if (isEnabled()) { mKitManager.roktKitApi?.getSessionId() } else { null @@ -111,11 +116,18 @@ class Rokt internal constructor(private val mConfigManager: ConfigManager, priva * @param attributes The attributes to prepare */ fun prepareAttributesAsync(attributes: Map) { - if (mConfigManager.isEnabled) { + if (isEnabled()) { mKitManager.roktKitApi?.prepareAttributesAsync(attributes) } } + private fun isEnabled(): Boolean = try { + val field = mConfigManager.javaClass.getMethod("isEnabled") + field.invoke(mConfigManager) as? Boolean ?: false + } catch (ignored: Exception) { + false + } + private fun buildPlacementOptions(): PlacementOptions = PlacementOptions( jointSdkSelectPlacements = System.currentTimeMillis(), ) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt index 401cc92d0..508f437ca 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt @@ -22,7 +22,5 @@ object MParticleRokt { private fun createRokt(mParticle: MParticle): Rokt { val configManager = mParticle.Internal().configManager val kitManager = mParticle.Internal().kitManager - val constructor = Rokt::class.java.getDeclaredConstructor(configManager::class.java, kitManager::class.java) - constructor.isAccessible = true - return constructor.newInstance(configManager, kitManager) + return Rokt(configManager, kitManager) } diff --git a/android-core/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt similarity index 90% rename from android-core/src/test/kotlin/com/mparticle/RoktTest.kt rename to kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt index 317f58626..ea7cf8032 100644 --- a/android-core/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt @@ -3,7 +3,6 @@ package com.mparticle import android.graphics.Typeface import android.os.Looper import android.os.SystemClock -import com.mparticle.internal.ConfigManager import com.mparticle.internal.KitManager import com.mparticle.internal.RoktKitApi import com.mparticle.rokt.PlacementOptions @@ -40,10 +39,13 @@ class RoktTest { @Mock lateinit var roktKitApi: RoktKitApi - @Mock - lateinit var configManager: ConfigManager + private lateinit var configManager: FakeConfigManager private lateinit var rokt: Rokt + class FakeConfigManager(var enabled: Boolean = true) { + fun isEnabled(): Boolean = enabled + } + // Helpers to make Mockito matchers work in Kotlin with non-nullable types. // Mockito matchers return null, which Kotlin rejects for non-nullable params. // These helpers call the matcher (to register it) then return a cast null. @@ -68,12 +70,13 @@ class RoktTest { @Before fun setUp() { MockitoAnnotations.initMocks(this) + configManager = FakeConfigManager(enabled = true) rokt = Rokt(configManager, kitManager) } @Test fun testSelectPlacements_withFullParams_whenEnabled() { - `when`(configManager.isEnabled).thenReturn(true) + configManager.enabled = true `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val attributes = mutableMapOf() @@ -124,7 +127,7 @@ class RoktTest { @Test fun testSelectPlacements_withBasicParams_whenEnabled() { - `when`(configManager.isEnabled()).thenReturn(true) + configManager.enabled = true `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val attributes = mutableMapOf() @@ -145,7 +148,7 @@ class RoktTest { @Test fun testSelectPlacements_withBasicParams_whenDisabled() { - `when`(configManager.isEnabled()).thenReturn(false) + configManager.enabled = false `when`(kitManager.roktKitApi).thenReturn(roktKitApi) rokt.selectPlacements( @@ -158,7 +161,7 @@ class RoktTest { @Test fun testRoktSetWrapperSdk_whenDisabled_kitManagerNotCalled() { - `when`(configManager.isEnabled()).thenReturn(false) + configManager.enabled = false rokt.selectPlacements( identifier = "basicView", @@ -170,7 +173,7 @@ class RoktTest { @Test fun testReportConversion_withBasicParams_whenEnabled() { - `when`(configManager.isEnabled()).thenReturn(true) + configManager.enabled = true `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val attributes = mutableMapOf() @@ -183,7 +186,7 @@ class RoktTest { @Test fun testReportConversion_withBasicParams_whenDisabled() { - `when`(configManager.isEnabled()).thenReturn(false) + configManager.enabled = false `when`(kitManager.roktKitApi).thenReturn(roktKitApi) rokt.purchaseFinalized("132", "1111", true) @@ -193,7 +196,7 @@ class RoktTest { @Test fun testEvents_whenEnabled_delegatesToKitManager() { - `when`(configManager.isEnabled).thenReturn(true) + configManager.enabled = true `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val testIdentifier = "test-identifier" @@ -208,7 +211,7 @@ class RoktTest { @Test fun testEvents_whenDisabled_returnsEmptyFlow() { - `when`(configManager.isEnabled).thenReturn(false) + configManager.enabled = false `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val testIdentifier = "test-identifier" @@ -224,7 +227,7 @@ class RoktTest { @Test fun testSetSessionId_whenEnabled_delegatesToKitManager() { - `when`(configManager.isEnabled).thenReturn(true) + configManager.enabled = true `when`(kitManager.roktKitApi).thenReturn(roktKitApi) rokt.setSessionId("test-session-id") verify(roktKitApi).setSessionId("test-session-id") @@ -232,7 +235,7 @@ class RoktTest { @Test fun testSetSessionId_whenDisabled_doesNotCallKitManager() { - `when`(configManager.isEnabled).thenReturn(false) + configManager.enabled = false `when`(kitManager.roktKitApi).thenReturn(roktKitApi) rokt.setSessionId("test-session-id") verify(roktKitApi, never()).setSessionId(any()) @@ -240,7 +243,7 @@ class RoktTest { @Test fun testGetSessionId_whenEnabled_delegatesToKitManager() { - `when`(configManager.isEnabled).thenReturn(true) + configManager.enabled = true `when`(kitManager.roktKitApi).thenReturn(roktKitApi) `when`(roktKitApi.getSessionId()).thenReturn("expected-session-id") val result = rokt.getSessionId() @@ -250,7 +253,7 @@ class RoktTest { @Test fun testGetSessionId_whenDisabled_returnsNull() { - `when`(configManager.isEnabled).thenReturn(false) + configManager.enabled = false `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val result = rokt.getSessionId() verify(roktKitApi, never()).getSessionId() @@ -259,7 +262,7 @@ class RoktTest { @Test fun testSelectPlacements_withOptions_whenEnabled() { - `when`(configManager.isEnabled).thenReturn(true) + configManager.enabled = true `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val currentTimeMillis = System.currentTimeMillis() From fdcbfdf96fca923042d8f7ee663bb2ab8076ebf4 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Tue, 12 May 2026 14:26:45 -0400 Subject: [PATCH 03/23] refactor: route Rokt facade calls through resolveRoktKit Use a single helper in the Rokt facade to resolve the kit API for all operations, making the upcoming decoupling from the legacy roktKitApi chain incremental and safer. --- .../rokt/src/main/kotlin/com/mparticle/Rokt.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt index 496c2ad84..83bcaa2ad 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt @@ -32,7 +32,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi config: RoktConfig? = null, ) { if (isEnabled()) { - val roktApi = mKitManager.roktKitApi + val roktApi = resolveRoktKit() if (roktApi != null) { roktApi.selectPlacements( identifier, @@ -56,7 +56,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi * @return A Flow emitting RoktEvent objects */ fun events(identifier: String): Flow = if (isEnabled()) { - mKitManager.roktKitApi?.events(identifier) ?: flowOf() + resolveRoktKit()?.events(identifier) ?: flowOf() } else { flowOf() } @@ -70,7 +70,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi */ fun purchaseFinalized(placementId: String, catalogItemId: String, status: Boolean) { if (isEnabled()) { - mKitManager.roktKitApi?.purchaseFinalized(placementId, catalogItemId, status) + resolveRoktKit()?.purchaseFinalized(placementId, catalogItemId, status) } } @@ -79,7 +79,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi */ fun close() { if (isEnabled()) { - mKitManager.roktKitApi?.close() + resolveRoktKit()?.close() } } @@ -95,7 +95,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi */ fun setSessionId(sessionId: String) { if (isEnabled()) { - mKitManager.roktKitApi?.setSessionId(sessionId) + resolveRoktKit()?.setSessionId(sessionId) } } @@ -105,7 +105,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi * @return The session id or null if no session is present or SDK is not initialized. */ fun getSessionId(): String? = if (isEnabled()) { - mKitManager.roktKitApi?.getSessionId() + resolveRoktKit()?.getSessionId() } else { null } @@ -117,10 +117,12 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi */ fun prepareAttributesAsync(attributes: Map) { if (isEnabled()) { - mKitManager.roktKitApi?.prepareAttributesAsync(attributes) + resolveRoktKit()?.prepareAttributesAsync(attributes) } } + private fun resolveRoktKit() = mKitManager.roktKitApi + private fun isEnabled(): Boolean = try { val field = mConfigManager.javaClass.getMethod("isEnabled") field.invoke(mConfigManager) as? Boolean ?: false From a6739577dcf5e027a733738fc25f38a007d1f473 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Tue, 12 May 2026 14:35:45 -0400 Subject: [PATCH 04/23] refactor: move Rokt request preprocessing into rokt kit helper --- .../src/main/kotlin/com/mparticle/Rokt.kt | 54 +++-- .../mparticle/kits/RoktKitRequestHelper.kt | 203 ++++++++++++++++++ .../src/test/kotlin/com/mparticle/RoktTest.kt | 75 ++++--- 3 files changed, 284 insertions(+), 48 deletions(-) create mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt index 83bcaa2ad..db00354cf 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt @@ -3,6 +3,8 @@ package com.mparticle import android.graphics.Typeface import com.mparticle.internal.KitManager import com.mparticle.internal.Logger +import com.mparticle.kits.KitIntegration +import com.mparticle.kits.RoktKitRequestHelper import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView @@ -32,16 +34,19 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi config: RoktConfig? = null, ) { if (isEnabled()) { - val roktApi = resolveRoktKit() - if (roktApi != null) { - roktApi.selectPlacements( - identifier, - HashMap(attributes), - callbacks, - embeddedViews, - fontTypefaces, - config, - buildPlacementOptions(), + val resolved = resolveRoktKit() + if (resolved != null) { + val (kitIntegration, roktListener) = resolved + RoktKitRequestHelper.selectPlacements( + kitIntegration = kitIntegration, + roktListener = roktListener, + viewName = identifier, + attributes = HashMap(attributes), + mpRoktEventCallback = callbacks, + placeHolders = embeddedViews, + fontTypefaces = fontTypefaces, + config = config, + options = buildPlacementOptions(), ) } else { Logger.warning("Rokt Kit is not available. Make sure the Rokt Kit is included in your app.") @@ -56,7 +61,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi * @return A Flow emitting RoktEvent objects */ fun events(identifier: String): Flow = if (isEnabled()) { - resolveRoktKit()?.events(identifier) ?: flowOf() + resolveRoktKit()?.second?.events(identifier) ?: flowOf() } else { flowOf() } @@ -70,7 +75,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi */ fun purchaseFinalized(placementId: String, catalogItemId: String, status: Boolean) { if (isEnabled()) { - resolveRoktKit()?.purchaseFinalized(placementId, catalogItemId, status) + resolveRoktKit()?.second?.purchaseFinalized(placementId, catalogItemId, status) } } @@ -79,7 +84,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi */ fun close() { if (isEnabled()) { - resolveRoktKit()?.close() + resolveRoktKit()?.second?.close() } } @@ -95,7 +100,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi */ fun setSessionId(sessionId: String) { if (isEnabled()) { - resolveRoktKit()?.setSessionId(sessionId) + resolveRoktKit()?.second?.setSessionId(sessionId) } } @@ -105,7 +110,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi * @return The session id or null if no session is present or SDK is not initialized. */ fun getSessionId(): String? = if (isEnabled()) { - resolveRoktKit()?.getSessionId() + resolveRoktKit()?.second?.sessionId } else { null } @@ -117,11 +122,26 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi */ fun prepareAttributesAsync(attributes: Map) { if (isEnabled()) { - resolveRoktKit()?.prepareAttributesAsync(attributes) + val resolved = resolveRoktKit() + if (resolved != null) { + val (kitIntegration, roktListener) = resolved + RoktKitRequestHelper.prepareAttributesAsync( + kitIntegration = kitIntegration, + roktListener = roktListener, + attributes = attributes, + ) + } } } - private fun resolveRoktKit() = mKitManager.roktKitApi + private fun resolveRoktKit(): Pair? { + if (!mKitManager.isKitActive(MParticle.ServiceProviders.ROKT)) { + return null + } + val kitInstance = mKitManager.getKitInstance(MParticle.ServiceProviders.ROKT) as? KitIntegration ?: return null + val roktListener = kitInstance as? KitIntegration.RoktListener ?: return null + return kitInstance to roktListener + } private fun isEnabled(): Boolean = try { val field = mConfigManager.javaClass.getMethod("isEnabled") diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt new file mode 100644 index 000000000..e1fbeb0ec --- /dev/null +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt @@ -0,0 +1,203 @@ +package com.mparticle.kits + +import android.graphics.Typeface +import com.mparticle.MParticle +import com.mparticle.MpRoktEventCallback +import com.mparticle.identity.IdentityApi +import com.mparticle.identity.IdentityApiRequest +import com.mparticle.identity.MParticleUser +import com.mparticle.internal.Logger +import com.mparticle.internal.MPUtility +import com.mparticle.rokt.PlacementOptions +import com.mparticle.rokt.RoktConfig +import com.mparticle.rokt.RoktEmbeddedView +import org.json.JSONException +import java.lang.ref.WeakReference +import java.util.Objects + +internal object RoktKitRequestHelper { + fun selectPlacements( + kitIntegration: KitIntegration, + roktListener: KitIntegration.RoktListener, + viewName: String, + attributes: Map, + mpRoktEventCallback: MpRoktEventCallback?, + placeHolders: Map>?, + fontTypefaces: Map>?, + config: RoktConfig?, + options: PlacementOptions?, + ) { + val mutableAttributes = attributes.toMutableMap() + val instance = MParticle.getInstance() + if (instance == null) { + Logger.warning("MParticle instance is null, cannot execute Rokt placement") + return + } + val user = instance.Identity().currentUser + val email = getValueIgnoreCase(mutableAttributes, "email") + val hashedEmail = getValueIgnoreCase(mutableAttributes, "emailsha256") + val kitConfig = kitIntegration.configuration + + confirmEmail(email, hashedEmail, user, instance.Identity(), kitConfig) { + val finalAttributes = prepareAttributes(mutableAttributes, user, kitConfig) + roktListener.selectPlacements( + viewName, + finalAttributes, + mpRoktEventCallback, + placeHolders?.toMutableMap(), + fontTypefaces?.toMutableMap(), + FilteredMParticleUser.getInstance(user?.id ?: 0L, kitIntegration), + config, + options, + ) + } + } + + fun prepareAttributesAsync( + kitIntegration: KitIntegration, + roktListener: KitIntegration.RoktListener, + attributes: Map, + ) { + val mutableAttributes = attributes.toMutableMap() + val instance = MParticle.getInstance() + if (instance == null) { + Logger.warning("MParticle instance is null, cannot prepare attributes") + return + } + val user = instance.Identity().currentUser + val email = mutableAttributes["email"] + val hashedEmail = getValueIgnoreCase(mutableAttributes, "emailsha256") + val kitConfig = kitIntegration.configuration + + confirmEmail(email, hashedEmail, user, instance.Identity(), kitConfig) { + val finalAttributes = prepareAttributes(mutableAttributes, user, kitConfig) + roktListener.enrichAttributes( + finalAttributes, + FilteredMParticleUser.getInstance(user?.id ?: 0L, kitIntegration), + ) + } + } + + private fun getValueIgnoreCase(map: Map, searchKey: String): String? { + for ((key, value) in map) { + if (key.equals(searchKey, ignoreCase = true)) { + return value + } + } + return null + } + + private fun prepareAttributes( + finalAttributes: MutableMap, + user: MParticleUser?, + kitConfig: KitConfiguration?, + ): MutableMap { + val jsonArray = try { + kitConfig?.placementAttributesMapping ?: org.json.JSONArray() + } catch (e: JSONException) { + Logger.warning("Invalid placementAttributes for Rokt Kit JSON: ${e.message}") + org.json.JSONArray() + } + + for (i in 0 until jsonArray.length()) { + val obj = jsonArray.optJSONObject(i) ?: continue + val mapFrom = obj.optString("map") + val mapTo = obj.optString("value") + if (finalAttributes.containsKey(mapFrom)) { + val value = finalAttributes.remove(mapFrom) + if (value != null) { + finalAttributes[mapTo] = value + } + } + } + + val objectAttributes = mutableMapOf() + for ((key, value) in finalAttributes) { + if (key != ROKT_ATTRIBUTE_SANDBOX_MODE) { + objectAttributes[key] = value + } + } + user?.setUserAttributes(objectAttributes) + + if (!finalAttributes.containsKey(ROKT_ATTRIBUTE_SANDBOX_MODE)) { + finalAttributes[ROKT_ATTRIBUTE_SANDBOX_MODE] = + Objects.toString(MPUtility.isDevEnv(), "false") + } + return finalAttributes + } + + private fun confirmEmail( + email: String?, + hashedEmail: String?, + user: MParticleUser?, + identityApi: IdentityApi, + kitConfiguration: KitConfiguration?, + runnable: Runnable, + ) { + val hasEmail = !email.isNullOrEmpty() + val hasHashedEmail = !hashedEmail.isNullOrEmpty() + + if ((hasEmail || hasHashedEmail) && user != null) { + var selectedIdentityType: MParticle.IdentityType? = null + try { + val identityTypeStr = kitConfiguration?.hashedEmailUserIdentityType + if (identityTypeStr != null) { + selectedIdentityType = MParticle.IdentityType.valueOf(identityTypeStr) + } + } catch (e: IllegalArgumentException) { + Logger.error("Invalid identity type ${e.message}") + } + + val existingEmail = user.userIdentities[MParticle.IdentityType.Email] + val existingHashedEmail = selectedIdentityType?.let { user.userIdentities[it] } + val emailMismatch = hasEmail && !email.equals(existingEmail, ignoreCase = true) + val hashedEmailMismatch = + hasHashedEmail && !hashedEmail.equals(existingHashedEmail, ignoreCase = true) + + if (emailMismatch || (hashedEmailMismatch && selectedIdentityType != null)) { + if (emailMismatch && existingEmail != null) { + Logger.warning( + "The existing email on the user ($existingEmail) does not match the email passed to selectPlacements ($email). " + + "Please make sure to sync the email identity to mParticle as soon as it's available. " + + "Identifying user with the provided email before continuing to selectPlacements.", + ) + } else if (hashedEmailMismatch && existingHashedEmail != null) { + Logger.warning( + "The existing hashed email on the user ($existingHashedEmail) does not match " + + "the hashed email passed to selectPlacements ($hashedEmail). " + + "Please make sure to sync the hashed email identity to mParticle as soon as it's available. " + + "Identifying user with the provided hashed email before continuing to selectPlacements.", + ) + } + + val identityBuilder = IdentityApiRequest.withUser(user) + if (emailMismatch) { + identityBuilder.email(email) + } + if (hashedEmailMismatch && selectedIdentityType != null) { + identityBuilder.userIdentity(selectedIdentityType, hashedEmail) + } + + val identityRequest = identityBuilder.build() + val task = identityApi.identify(identityRequest) + + task.addFailureListener { result -> + Logger.error("Failed to sync email from selectPlacement to user: ${result?.errors}") + runnable.run() + } + + task.addSuccessListener { result -> + Logger.debug( + "Updated email identity based on selectPlacement's attributes: " + + result.user.userIdentities[MParticle.IdentityType.Email], + ) + runnable.run() + } + } else { + runnable.run() + } + } else { + runnable.run() + } + } +} diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt index ea7cf8032..f37df44ac 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt @@ -3,8 +3,10 @@ package com.mparticle import android.graphics.Typeface import android.os.Looper import android.os.SystemClock +import com.mparticle.identity.IdentityApi +import com.mparticle.identity.MParticleUser import com.mparticle.internal.KitManager -import com.mparticle.internal.RoktKitApi +import com.mparticle.kits.KitIntegration import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView @@ -22,6 +24,7 @@ import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` +import org.mockito.Mockito.withSettings import org.mockito.MockitoAnnotations import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner @@ -37,7 +40,16 @@ class RoktTest { lateinit var kitManager: KitManager @Mock - lateinit var roktKitApi: RoktKitApi + lateinit var identityApi: IdentityApi + + @Mock + lateinit var mParticle: MParticle + + @Mock + lateinit var mParticleUser: MParticleUser + + private lateinit var roktKit: KitIntegration + private lateinit var roktListener: KitIntegration.RoktListener private lateinit var configManager: FakeConfigManager private lateinit var rokt: Rokt @@ -71,13 +83,23 @@ class RoktTest { fun setUp() { MockitoAnnotations.initMocks(this) configManager = FakeConfigManager(enabled = true) + roktKit = + org.mockito.Mockito.mock( + KitIntegration::class.java, + withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), + ) + roktListener = roktKit as KitIntegration.RoktListener + MParticle.setInstance(mParticle) + `when`(mParticle.Identity()).thenReturn(identityApi) + `when`(identityApi.currentUser).thenReturn(mParticleUser) + `when`(kitManager.isKitActive(MParticle.ServiceProviders.ROKT)).thenReturn(true) + `when`(kitManager.getKitInstance(MParticle.ServiceProviders.ROKT)).thenReturn(roktKit) rokt = Rokt(configManager, kitManager) } @Test fun testSelectPlacements_withFullParams_whenEnabled() { configManager.enabled = true - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val attributes = mutableMapOf() attributes["key"] = "value" @@ -114,9 +136,10 @@ class RoktTest { config = config, ) - verify(roktKitApi).selectPlacements( + verify(roktListener).selectPlacements( eq("testView"), - eq(attributes), + any(), + any(), any(), any(), any(), @@ -128,35 +151,34 @@ class RoktTest { @Test fun testSelectPlacements_withBasicParams_whenEnabled() { configManager.enabled = true - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val attributes = mutableMapOf() attributes["a"] = "b" rokt.selectPlacements(attributes = attributes, identifier = "basicView") - verify(roktKitApi).selectPlacements( + verify(roktListener).selectPlacements( eq("basicView"), - eq(attributes), + any(), isNull(), isNull(), isNull(), isNull(), any(), + any(), ) } @Test fun testSelectPlacements_withBasicParams_whenDisabled() { configManager.enabled = false - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) rokt.selectPlacements( identifier = "basicView", attributes = HashMap(), ) - verify(roktKitApi, never()).selectPlacements(any(), any(), any(), any(), any(), any(), any()) + verify(roktListener, never()).selectPlacements(any(), any(), any(), any(), any(), any(), any(), any()) } @Test @@ -174,51 +196,47 @@ class RoktTest { @Test fun testReportConversion_withBasicParams_whenEnabled() { configManager.enabled = true - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val attributes = mutableMapOf() attributes["a"] = "b" rokt.purchaseFinalized("132", "1111", true) - verify(roktKitApi).purchaseFinalized("132", "1111", true) + verify(roktListener).purchaseFinalized("132", "1111", true) } @Test fun testReportConversion_withBasicParams_whenDisabled() { configManager.enabled = false - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) rokt.purchaseFinalized("132", "1111", true) - verify(roktKitApi, never()).purchaseFinalized("132", "1111", true) + verify(roktListener, never()).purchaseFinalized("132", "1111", true) } @Test fun testEvents_whenEnabled_delegatesToKitManager() { configManager.enabled = true - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val testIdentifier = "test-identifier" val expectedFlow: Flow = flowOf() - `when`(roktKitApi.events(testIdentifier)).thenReturn(expectedFlow) + `when`(roktListener.events(testIdentifier)).thenReturn(expectedFlow) val result = rokt.events(testIdentifier) - verify(roktKitApi).events(testIdentifier) + verify(roktListener).events(testIdentifier) assertEquals(expectedFlow, result) } @Test fun testEvents_whenDisabled_returnsEmptyFlow() { configManager.enabled = false - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val testIdentifier = "test-identifier" val result = rokt.events(testIdentifier) - verify(roktKitApi, never()).events(any()) + verify(roktListener, never()).events(any()) runTest { val elements = result.toList() assertTrue(elements.isEmpty()) @@ -228,42 +246,37 @@ class RoktTest { @Test fun testSetSessionId_whenEnabled_delegatesToKitManager() { configManager.enabled = true - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) rokt.setSessionId("test-session-id") - verify(roktKitApi).setSessionId("test-session-id") + verify(roktListener).setSessionId("test-session-id") } @Test fun testSetSessionId_whenDisabled_doesNotCallKitManager() { configManager.enabled = false - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) rokt.setSessionId("test-session-id") - verify(roktKitApi, never()).setSessionId(any()) + verify(roktListener, never()).setSessionId(any()) } @Test fun testGetSessionId_whenEnabled_delegatesToKitManager() { configManager.enabled = true - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) - `when`(roktKitApi.getSessionId()).thenReturn("expected-session-id") + `when`(roktListener.sessionId).thenReturn("expected-session-id") val result = rokt.getSessionId() - verify(roktKitApi).getSessionId() + verify(roktListener).getSessionId() assertEquals("expected-session-id", result) } @Test fun testGetSessionId_whenDisabled_returnsNull() { configManager.enabled = false - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val result = rokt.getSessionId() - verify(roktKitApi, never()).getSessionId() + verify(roktListener, never()).getSessionId() assertNull(result) } @Test fun testSelectPlacements_withOptions_whenEnabled() { configManager.enabled = true - `when`(kitManager.roktKitApi).thenReturn(roktKitApi) val currentTimeMillis = System.currentTimeMillis() val attributes = mutableMapOf() @@ -274,14 +287,14 @@ class RoktTest { ) // Verify call is forwarded - val viewNameCaptor = ArgumentCaptor.forClass(String::class.java) val optionsCaptor = ArgumentCaptor.forClass(PlacementOptions::class.java) - verify(roktKitApi).selectPlacements( + verify(roktListener).selectPlacements( eq("testView"), any(), isNull(), isNull(), isNull(), + any(), isNull(), capture(optionsCaptor), ) From e68f7fdfe525a26d280601367094ffb3d05ca0d6 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Tue, 12 May 2026 16:04:22 -0400 Subject: [PATCH 05/23] refactor: route RoktKit attribute preparation through kit helper --- .../rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt index 091ba6356..eda0dfd82 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt @@ -362,9 +362,12 @@ class RoktKit : mpRoktEventCallback: MpRoktEventCallback?, onResult: (Map, RoktCallback) -> Unit, ) { - val instance = MParticle.getInstance() deferredAttributes = CompletableDeferred() - instance?.Internal()?.kitManager?.roktKitApi?.prepareAttributesAsync(attributes) + RoktKitRequestHelper.prepareAttributesAsync( + kitIntegration = this, + roktListener = this, + attributes = attributes, + ) this.mpRoktEventCallback = mpRoktEventCallback CoroutineScope(Dispatchers.Default).launch { val resultAttributes = deferredAttributes!!.await() From baf95a9b26cddc98ea8d0109987494c60d9f72d0 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Tue, 12 May 2026 16:08:04 -0400 Subject: [PATCH 06/23] refactor: remove roktKitApi from core kit manager contract --- .../internal/KitFrameworkWrapper.java | 9 ---- .../com/mparticle/internal/KitManager.java | 8 ---- .../mparticle/external/ApiVisibilityTest.kt | 2 +- .../internal/KitFrameworkWrapperTest.kt | 46 ------------------- .../com/mparticle/kits/KitManagerImpl.java | 1 - 5 files changed, 1 insertion(+), 65 deletions(-) diff --git a/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java b/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java index 1065d4102..6dd1b4768 100644 --- a/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java +++ b/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java @@ -662,15 +662,6 @@ public void reset() { } } - @Override - @Nullable - public RoktKitApi getRoktKitApi() { - if (mKitManager != null) { - return mKitManager.getRoktKitApi(); - } - return null; - } - @Override public void setWrapperSdkVersion(@NonNull WrapperSdkVersion wrapperSdkVersion) { if (mKitManager != null) { diff --git a/android-core/src/main/java/com/mparticle/internal/KitManager.java b/android-core/src/main/java/com/mparticle/internal/KitManager.java index b821bdd73..28009fa15 100644 --- a/android-core/src/main/java/com/mparticle/internal/KitManager.java +++ b/android-core/src/main/java/com/mparticle/internal/KitManager.java @@ -127,14 +127,6 @@ public interface KitManager { void reset(); - /** - * Get the RoktKitApi implementation if available. - * - * @return RoktKitApi instance or null if Rokt Kit is not configured or active - */ - @Nullable - RoktKitApi getRoktKitApi(); - void setWrapperSdkVersion(@NonNull WrapperSdkVersion wrapperSdkVersion); enum KitStatus { diff --git a/android-core/src/test/kotlin/com/mparticle/external/ApiVisibilityTest.kt b/android-core/src/test/kotlin/com/mparticle/external/ApiVisibilityTest.kt index 3d2019a31..d796e4de2 100644 --- a/android-core/src/test/kotlin/com/mparticle/external/ApiVisibilityTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/external/ApiVisibilityTest.kt @@ -17,7 +17,7 @@ class ApiVisibilityTest { publicMethodCount++ } } - Assert.assertEquals(63, publicMethodCount) + Assert.assertEquals(62, publicMethodCount) } @Test diff --git a/android-core/src/test/kotlin/com/mparticle/internal/KitFrameworkWrapperTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/KitFrameworkWrapperTest.kt index f1b327c9d..1804a3eb8 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/KitFrameworkWrapperTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/KitFrameworkWrapperTest.kt @@ -29,7 +29,6 @@ import org.powermock.modules.junit4.PowerMockRunner import java.lang.ref.WeakReference import java.util.Random import kotlin.test.assertEquals -import kotlin.test.assertNull @RunWith(PowerMockRunner::class) class KitFrameworkWrapperTest { @@ -721,49 +720,4 @@ class KitFrameworkWrapperTest { verify(mockKitManager).setWrapperSdkVersion(expectedSdkVersion) } - - @Test - fun testGetRoktKitApi_kitManagerNull_returnsNull() { - val wrapper = - KitFrameworkWrapper( - mock( - Context::class.java, - ), - mock(ReportingManager::class.java), - mock(ConfigManager::class.java), - mock(AppStateManager::class.java), - true, - mock(MParticleOptions::class.java), - ) - - val result = wrapper.roktKitApi - - assertNull(result) - } - - @Test - fun testGetRoktKitApi_kitManagerSet_delegatesToKitManager() { - val wrapper = - KitFrameworkWrapper( - mock( - Context::class.java, - ), - mock(ReportingManager::class.java), - mock(ConfigManager::class.java), - mock(AppStateManager::class.java), - true, - mock(MParticleOptions::class.java), - ) - - val mockKitManager = mock(KitManager::class.java) - val mockRoktKitApi = mock(RoktKitApi::class.java) - - `when`(mockKitManager.roktKitApi).thenReturn(mockRoktKitApi) - wrapper.setKitManager(mockKitManager) - - val result = wrapper.roktKitApi - - verify(mockKitManager).roktKitApi - assertEquals(mockRoktKitApi, result) - } } diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java index 1eba0cfac..b0fdc900a 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java @@ -1309,7 +1309,6 @@ public void reset() { } } - @Override @Nullable public RoktKitApi getRoktKitApi() { for (KitIntegration provider : activeKits()) { From 460aff05ccb7548a0e375fc9faddb63e327416b0 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Tue, 12 May 2026 16:18:24 -0400 Subject: [PATCH 07/23] refactor: remove remaining roktKitApi bridge and tests --- .../com/mparticle/internal/RoktKitApi.kt | 87 -- .../com/mparticle/kits/KitIntegration.java | 8 +- .../com/mparticle/kits/KitManagerImpl.java | 11 - .../com/mparticle/kits/RoktKitApiImpl.kt | 257 ----- .../com/mparticle/kits/KitManagerImplTest.kt | 913 ------------------ .../com/mparticle/kits/RoktKitApiImplTest.kt | 167 ---- 6 files changed, 2 insertions(+), 1441 deletions(-) delete mode 100644 android-core/src/main/kotlin/com/mparticle/internal/RoktKitApi.kt delete mode 100644 android-kit-base/src/main/kotlin/com/mparticle/kits/RoktKitApiImpl.kt delete mode 100644 android-kit-base/src/test/kotlin/com/mparticle/kits/RoktKitApiImplTest.kt diff --git a/android-core/src/main/kotlin/com/mparticle/internal/RoktKitApi.kt b/android-core/src/main/kotlin/com/mparticle/internal/RoktKitApi.kt deleted file mode 100644 index 68f9c628d..000000000 --- a/android-core/src/main/kotlin/com/mparticle/internal/RoktKitApi.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.mparticle.internal - -import android.graphics.Typeface -import com.mparticle.MpRoktEventCallback -import com.mparticle.RoktEvent -import com.mparticle.rokt.PlacementOptions -import com.mparticle.rokt.RoktConfig -import com.mparticle.rokt.RoktEmbeddedView -import kotlinx.coroutines.flow.Flow -import java.lang.ref.WeakReference - -/** - * Interface for Rokt Kit operations. - * - * Implementations of this interface are provided by the Rokt Kit when it is - * configured and active. Use [KitManager.getRoktKitApi] to obtain an instance. - */ -interface RoktKitApi { - /** - * Initiate a Rokt placement selection with the specified parameters. - * - * @param viewName The identifier for the placement view - * @param attributes User attributes to pass to Rokt - * @param mpRoktEventCallback Optional callback for Rokt events - * @param placeHolders Optional map of embedded view placeholders - * @param fontTypefaces Optional map of font typefaces - * @param config Optional Rokt configuration - * @param options Optional placement options - */ - fun selectPlacements( - viewName: String, - attributes: Map, - mpRoktEventCallback: MpRoktEventCallback?, - placeHolders: Map>?, - fontTypefaces: Map>?, - config: RoktConfig?, - options: PlacementOptions? = null, - ) - - /** - * Get a Flow of Rokt events for the specified identifier. - * - * @param identifier The placement identifier to listen for events - * @return A Flow emitting RoktEvent objects - */ - fun events(identifier: String): Flow - - /** - * Notify Rokt that a purchase has been finalized. - * - * @param placementId The placement identifier - * @param catalogItemId The catalog item identifier - * @param status Whether the purchase was successful - */ - fun purchaseFinalized(placementId: String, catalogItemId: String, status: Boolean) - - /** - * Close any active Rokt placements. - */ - fun close() - - /** - * Set the session id to use for the next execute call. - * - * This is useful for cases where you have a session id from a non-native integration, - * e.g. WebView, and you want the session to be consistent across integrations. - * - * **Note:** Empty strings are ignored and will not update the session. - * - * @param sessionId The session id to be set. Must be a non-empty string. - */ - fun setSessionId(sessionId: String) - - /** - * Get the session id to use within a non-native integration e.g. WebView. - * - * @return The session id or null if no session is present. - */ - fun getSessionId(): String? - - /** - * Prepare attributes asynchronously before executing a placement. - * - * @param attributes The attributes to prepare - */ - fun prepareAttributesAsync(attributes: Map) -} diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java index c7b35f924..18633a61c 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java @@ -590,12 +590,8 @@ public interface BatchListener { /** * Interface for Rokt Kit implementations. * - *

This interface is internal to kit-base and is bridged to the - * {@link com.mparticle.internal.RoktKitApi} interface via a wrapper implementation - * in {@link KitManagerImpl}. The wrapper handles user resolution and - * attribute preparation before delegating to the kit's methods.

- * - * @see com.mparticle.internal.RoktKitApi + *

This interface is internal to kit-base and is implemented by the Rokt kit to + * handle placement selection, event streaming, and session operations.

*/ public interface RoktListener { diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java index b0fdc900a..fb4662eb5 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java @@ -26,7 +26,6 @@ import com.mparticle.MPEvent; import com.mparticle.MParticle; import com.mparticle.MParticleOptions; -import com.mparticle.internal.RoktKitApi; import com.mparticle.WrapperSdkVersion; import com.mparticle.commerce.CommerceEvent; import com.mparticle.consent.ConsentState; @@ -1309,16 +1308,6 @@ public void reset() { } } - @Nullable - public RoktKitApi getRoktKitApi() { - for (KitIntegration provider : activeKits()) { - if (provider instanceof KitIntegration.RoktListener listener) { - return new RoktKitApiImpl(listener, provider); - } - } - return null; - } - @Override public void setWrapperSdkVersion(@NonNull WrapperSdkVersion wrapperSdkVersion) { for (KitIntegration provider : activeKits()) { diff --git a/android-kit-base/src/main/kotlin/com/mparticle/kits/RoktKitApiImpl.kt b/android-kit-base/src/main/kotlin/com/mparticle/kits/RoktKitApiImpl.kt deleted file mode 100644 index 68fd73d4c..000000000 --- a/android-kit-base/src/main/kotlin/com/mparticle/kits/RoktKitApiImpl.kt +++ /dev/null @@ -1,257 +0,0 @@ -package com.mparticle.kits - -import android.graphics.Typeface -import com.mparticle.MParticle -import com.mparticle.MpRoktEventCallback -import com.mparticle.RoktEvent -import com.mparticle.identity.IdentityApi -import com.mparticle.identity.IdentityApiRequest -import com.mparticle.identity.MParticleUser -import com.mparticle.internal.Constants -import com.mparticle.internal.Logger -import com.mparticle.internal.MPUtility -import com.mparticle.internal.RoktKitApi -import com.mparticle.rokt.PlacementOptions -import com.mparticle.rokt.RoktConfig -import com.mparticle.rokt.RoktEmbeddedView -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import org.json.JSONException -import java.lang.ref.WeakReference -import java.util.Objects - -/** - * Implementation of [RoktKitApi] that wraps a [KitIntegration.RoktListener]. - * - * This class handles user resolution and attribute preparation before delegating - * to the underlying Rokt Kit implementation. - */ -internal class RoktKitApiImpl(private val roktListener: KitIntegration.RoktListener, private val kitIntegration: KitIntegration) : RoktKitApi { - - override fun selectPlacements( - viewName: String, - attributes: Map, - mpRoktEventCallback: MpRoktEventCallback?, - placeHolders: Map>?, - fontTypefaces: Map>?, - config: RoktConfig?, - options: PlacementOptions?, - ) { - try { - val mutableAttributes = attributes.toMutableMap() - val instance = MParticle.getInstance() - if (instance == null) { - Logger.warning("MParticle instance is null, cannot execute Rokt placement") - return - } - val user = instance.Identity().currentUser - val email = getValueIgnoreCase(mutableAttributes, "email") - val hashedEmail = getValueIgnoreCase(mutableAttributes, "emailsha256") - val kitConfig = kitIntegration.configuration - - confirmEmail(email, hashedEmail, user, instance.Identity(), kitConfig) { - val finalAttributes = prepareAttributes(mutableAttributes, user) - roktListener.selectPlacements( - viewName, - finalAttributes, - mpRoktEventCallback, - placeHolders, - fontTypefaces, - FilteredMParticleUser.getInstance(user?.id ?: 0L, kitIntegration), - config, - options, - ) - } - } catch (e: Exception) { - Logger.warning("Failed to call execute for Rokt Kit: ${e.message}") - } - } - - override fun events(identifier: String): Flow = try { - Logger.verbose("Calling events for Rokt Kit with identifier: $identifier") - roktListener.events(identifier) - } catch (e: Exception) { - Logger.warning("Failed to call events for Rokt Kit: ${e.message}") - flowOf() - } - - override fun purchaseFinalized(placementId: String, catalogItemId: String, status: Boolean) { - try { - roktListener.purchaseFinalized(placementId, catalogItemId, status) - } catch (e: Exception) { - Logger.warning("Failed to call purchaseFinalized for Rokt Kit: ${e.message}") - } - } - - override fun close() { - try { - roktListener.close() - } catch (e: Exception) { - Logger.warning("Failed to call close for Rokt Kit: ${e.message}") - } - } - - override fun setSessionId(sessionId: String) { - try { - roktListener.setSessionId(sessionId) - } catch (e: Exception) { - Logger.warning("Failed to call setSessionId for Rokt Kit: ${e.message}") - } - } - - override fun getSessionId(): String? = try { - roktListener.sessionId - } catch (e: Exception) { - Logger.warning("Failed to call getSessionId for Rokt Kit: ${e.message}") - null - } - - override fun prepareAttributesAsync(attributes: Map) { - try { - val mutableAttributes = attributes.toMutableMap() - val instance = MParticle.getInstance() - if (instance == null) { - Logger.warning("MParticle instance is null, cannot prepare attributes") - return - } - val user = instance.Identity().currentUser - val email = mutableAttributes["email"] - val hashedEmail = getValueIgnoreCase(mutableAttributes, "emailsha256") - val kitConfig = kitIntegration.configuration - - confirmEmail(email, hashedEmail, user, instance.Identity(), kitConfig) { - val finalAttributes = prepareAttributes(mutableAttributes, user) - roktListener.enrichAttributes( - finalAttributes, - FilteredMParticleUser.getInstance(user?.id ?: 0L, kitIntegration), - ) - } - } catch (e: Exception) { - Logger.warning("Failed to call prepareAttributesAsync for Rokt Kit: ${e.message}") - } - } - - // Helper methods - - private fun getValueIgnoreCase(map: Map, searchKey: String): String? { - for ((key, value) in map) { - if (key.equals(searchKey, ignoreCase = true)) { - return value - } - } - return null - } - - private fun prepareAttributes(finalAttributes: MutableMap, user: MParticleUser?): MutableMap { - val kitConfig = kitIntegration.configuration - val jsonArray = try { - kitConfig?.placementAttributesMapping ?: org.json.JSONArray() - } catch (e: JSONException) { - Logger.warning("Invalid placementAttributes for Rokt Kit JSON: ${e.message}") - org.json.JSONArray() - } - - for (i in 0 until jsonArray.length()) { - val obj = jsonArray.optJSONObject(i) ?: continue - val mapFrom = obj.optString("map") - val mapTo = obj.optString("value") - if (finalAttributes.containsKey(mapFrom)) { - val value = finalAttributes.remove(mapFrom) - if (value != null) { - finalAttributes[mapTo] = value - } - } - } - - val objectAttributes = mutableMapOf() - for ((key, value) in finalAttributes) { - if (key != Constants.MessageKey.SANDBOX_MODE_ROKT) { - objectAttributes[key] = value - } - } - user?.setUserAttributes(objectAttributes) - - if (!finalAttributes.containsKey(Constants.MessageKey.SANDBOX_MODE_ROKT)) { - finalAttributes[Constants.MessageKey.SANDBOX_MODE_ROKT] = - Objects.toString(MPUtility.isDevEnv(), "false") - } - return finalAttributes - } - - private fun confirmEmail( - email: String?, - hashedEmail: String?, - user: MParticleUser?, - identityApi: IdentityApi, - kitConfiguration: KitConfiguration?, - runnable: Runnable, - ) { - val hasEmail = !email.isNullOrEmpty() - val hasHashedEmail = !hashedEmail.isNullOrEmpty() - - if ((hasEmail || hasHashedEmail) && user != null) { - var selectedIdentityType: MParticle.IdentityType? = null - try { - val identityTypeStr = kitConfiguration?.hashedEmailUserIdentityType - if (identityTypeStr != null) { - selectedIdentityType = MParticle.IdentityType.valueOf(identityTypeStr) - } - } catch (e: IllegalArgumentException) { - Logger.error("Invalid identity type ${e.message}") - } - - val existingEmail = user.userIdentities[MParticle.IdentityType.Email] - val existingHashedEmail = selectedIdentityType?.let { user.userIdentities[it] } - val emailMismatch = hasEmail && !email.equals(existingEmail, ignoreCase = true) - val hashedEmailMismatch = - hasHashedEmail && !hashedEmail.equals(existingHashedEmail, ignoreCase = true) - - if (emailMismatch || (hashedEmailMismatch && selectedIdentityType != null)) { - // If there's an existing email but it doesn't match the passed-in email, log a warning - if (emailMismatch && existingEmail != null) { - Logger.warning( - "The existing email on the user ($existingEmail) does not match the email passed to selectPlacements ($email). " + - "Please make sure to sync the email identity to mParticle as soon as it's available. " + - "Identifying user with the provided email before continuing to selectPlacements.", - ) - } else if (hashedEmailMismatch && existingHashedEmail != null) { - // If there's an existing other but it doesn't match the passed-in hashed email, log a warning - Logger.warning( - "The existing hashed email on the user ($existingHashedEmail) does not match " + - "the hashed email passed to selectPlacements ($hashedEmail). " + - "Please make sure to sync the hashed email identity to mParticle as soon as it's available. " + - "Identifying user with the provided hashed email before continuing to selectPlacements.", - ) - } - - val identityBuilder = IdentityApiRequest.withUser(user) - if (emailMismatch) { - identityBuilder.email(email) - } - if (hashedEmailMismatch && selectedIdentityType != null) { - identityBuilder.userIdentity(selectedIdentityType, hashedEmail) - } - - val identityRequest = identityBuilder.build() - val task = identityApi.identify(identityRequest) - - task.addFailureListener { result -> - Logger.error("Failed to sync email from selectPlacement to user: ${result?.errors}") - runnable.run() - } - - task.addSuccessListener { result -> - Logger.debug( - "Updated email identity based on selectPlacement's attributes: " + - result.user.userIdentities[MParticle.IdentityType.Email], - ) - runnable.run() - } - } else { - runnable.run() - } - } else { - runnable.run() - } - } -} diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt index 95de0b0c5..7e57fd27b 100644 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt +++ b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt @@ -910,617 +910,6 @@ class KitManagerImplTest { Assert.assertEquals(2, manager.providers.size) } - @Test - fun testRokt_non_standard_partner_user_attrs() { - val sideloadedKit = mock(MPSideloadedKit::class.java) - val kitId = 6000000 - - val configJSONObj = - JSONObject().apply { - put("id", kitId) - } - val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) - `when`(sideloadedKit.configuration).thenReturn(mockedKitConfig) - - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [ - {"map": "number", "value": "no"}, - {"map": "customerId", "value": "minorcatid"} - ] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(mockedKitConfig, settingsMap) - - val mockedProvider = MockProvider(mockedKitConfig) - - val options = - MParticleOptions - .builder(MockContext()) - .sideloadedKits(mutableListOf(sideloadedKit) as List) - .build() - val manager: KitManagerImpl = MockKitManagerImpl(options) - val factory = mock(KitIntegrationFactory::class.java) - manager.setKitFactory(factory) - - `when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) - val supportedKit = mutableSetOf(kitId) - `when`(manager.supportedKits).thenReturn(supportedKit) - `when`(sideloadedKit.isDisabled).thenReturn(false) - `when`( - factory.createInstance( - any( - KitManagerImpl::class.java, - ), - any(KitConfiguration::class.java), - ), - ).thenReturn(sideloadedKit) - manager.providers = - ConcurrentHashMap().apply { - put(42, mockedProvider) - } - - val attributes = - hashMapOf( - Pair("test", "Test"), - Pair("lastname", "Test1"), - Pair("number", "(123) 456-9898"), - Pair("customerId", "55555"), - Pair("country", "US"), - ) - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null) - val finalAttributes = mockedProvider.lastAttributes - Assert.assertNotNull(finalAttributes) - finalAttributes!! - Assert.assertEquals(6, finalAttributes.size) - Assert.assertEquals("(123) 456-9898", finalAttributes["no"]) - Assert.assertEquals("55555", finalAttributes["minorcatid"]) - Assert.assertEquals("Test1", finalAttributes["lastname"]) - Assert.assertEquals("Test", finalAttributes["test"]) - Assert.assertEquals("US", finalAttributes["country"]) - Assert.assertEquals("false", finalAttributes["sandbox"]) - } - - @Test - fun testSelectPlacements_shouldNotModifyAttributes_ifMappedKeysDoNotExist() { - val sideloadedKit = mock(MPSideloadedKit::class.java) - val kitId = 6000000 - - val configJSONObj = - JSONObject().apply { - put("id", kitId) - } - val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) - `when`(sideloadedKit.configuration).thenReturn(mockedKitConfig) - - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [ - {"map": "number", "value": "no"}, - {"map": "customerId", "value": "minorcatid"} - ] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(mockedKitConfig, settingsMap) - - val mockedProvider = MockProvider(mockedKitConfig) - - val options = - MParticleOptions - .builder(MockContext()) - .sideloadedKits(mutableListOf(sideloadedKit) as List) - .build() - val manager: KitManagerImpl = MockKitManagerImpl(options) - val factory = mock(KitIntegrationFactory::class.java) - manager.setKitFactory(factory) - - `when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) - val supportedKit = mutableSetOf(kitId) - `when`(manager.supportedKits).thenReturn(supportedKit) - `when`(sideloadedKit.isDisabled).thenReturn(false) - `when`( - factory.createInstance( - any( - KitManagerImpl::class.java, - ), - any(KitConfiguration::class.java), - ), - ).thenReturn(sideloadedKit) - manager.providers = - ConcurrentHashMap().apply { - put(42, mockedProvider) - } - - val attributes = - hashMapOf( - Pair("test", "Test"), - Pair("lastname", "Test1"), - Pair("call", "(123) 456-9898"), - Pair("postal", "5-45555"), - Pair("country", "US"), - ) - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null) - val finalAttributes = mockedProvider.lastAttributes - Assert.assertNotNull(finalAttributes) - finalAttributes!! - Assert.assertEquals(6, finalAttributes.size) - - Assert.assertEquals("(123) 456-9898", finalAttributes["call"]) - Assert.assertEquals("5-45555", finalAttributes["postal"]) - Assert.assertEquals("Test1", finalAttributes["lastname"]) - Assert.assertEquals("Test", finalAttributes["test"]) - Assert.assertEquals("US", finalAttributes["country"]) - Assert.assertEquals("false", finalAttributes["sandbox"]) - } - - @Test - fun testSelectPlacements_shouldNotModifyAttributes_ifMapAndValueKeysAreSame() { - val sideloadedKit = mock(MPSideloadedKit::class.java) - val kitId = 6000000 - - val configJSONObj = - JSONObject().apply { - put("id", kitId) - } - val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) - `when`(sideloadedKit.configuration).thenReturn(mockedKitConfig) - - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [ - {"map": "number", "value": "no"}, - {"map": "customerId", "value": "minorcatid"} - ] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(mockedKitConfig, settingsMap) - - val mockedProvider = MockProvider(mockedKitConfig) - - val options = - MParticleOptions - .builder(MockContext()) - .sideloadedKits(mutableListOf(sideloadedKit) as List) - .build() - val manager: KitManagerImpl = MockKitManagerImpl(options) - val factory = mock(KitIntegrationFactory::class.java) - manager.setKitFactory(factory) - - `when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) - val supportedKit = mutableSetOf(kitId) - `when`(manager.supportedKits).thenReturn(supportedKit) - `when`(sideloadedKit.isDisabled).thenReturn(false) - `when`( - factory.createInstance( - any( - KitManagerImpl::class.java, - ), - any(KitConfiguration::class.java), - ), - ).thenReturn(sideloadedKit) - manager.providers = - ConcurrentHashMap().apply { - put(42, mockedProvider) - } - - val attributes = - hashMapOf( - Pair("test", "Test"), - Pair("lastname", "Test1"), - Pair("no", "(123) 456-9898"), - Pair("minorcatid", "5-45555"), - Pair("country", "US"), - ) - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null) - val finalAttributes = mockedProvider.lastAttributes - Assert.assertNotNull(finalAttributes) - finalAttributes!! - Assert.assertEquals(6, finalAttributes.size) - Assert.assertEquals("(123) 456-9898", finalAttributes["no"]) - Assert.assertEquals("5-45555", finalAttributes["minorcatid"]) - Assert.assertEquals("Test1", finalAttributes["lastname"]) - Assert.assertEquals("Test", finalAttributes["test"]) - Assert.assertEquals("US", finalAttributes["country"]) - Assert.assertEquals("false", finalAttributes["sandbox"]) - } - - @Test - fun testRokt_non_standard_partner_user_attrs_When_placementAttributes_is_empty() { - val sideloadedKit = mock(MPSideloadedKit::class.java) - val kitId = 6000000 - - val configJSONObj = - JSONObject().apply { - put("id", kitId) - } - val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) - `when`(sideloadedKit.configuration).thenReturn(mockedKitConfig) - - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [ - ] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(mockedKitConfig, settingsMap) - - val mockedProvider = MockProvider(mockedKitConfig) - - val options = - MParticleOptions - .builder(MockContext()) - .sideloadedKits(mutableListOf(sideloadedKit) as List) - .build() - val manager: KitManagerImpl = MockKitManagerImpl(options) - val factory = mock(KitIntegrationFactory::class.java) - manager.setKitFactory(factory) - - `when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) - val supportedKit = mutableSetOf(kitId) - `when`(manager.supportedKits).thenReturn(supportedKit) - `when`(sideloadedKit.isDisabled).thenReturn(false) - `when`( - factory.createInstance( - any( - KitManagerImpl::class.java, - ), - any(KitConfiguration::class.java), - ), - ).thenReturn(sideloadedKit) - manager.providers = - ConcurrentHashMap().apply { - put(42, mockedProvider) - } - - val attributes = - hashMapOf( - Pair("test", "Test"), - Pair("lastname", "Test1"), - Pair("number", "(123) 456-9898"), - Pair("customerId", "55555"), - Pair("country", "US"), - ) - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null) - val finalAttributes = mockedProvider.lastAttributes - Assert.assertNotNull(finalAttributes) - finalAttributes!! - Assert.assertEquals(6, finalAttributes.size) - Assert.assertEquals("(123) 456-9898", finalAttributes["number"]) - Assert.assertEquals("55555", finalAttributes["customerId"]) - Assert.assertEquals("Test1", finalAttributes["lastname"]) - Assert.assertEquals("Test", finalAttributes["test"]) - Assert.assertEquals("US", finalAttributes["country"]) - Assert.assertEquals("false", finalAttributes["sandbox"]) - } - - @Test - fun testRokt_SandboxMode_When_Default_Environment() { - val sideloadedKit = mock(MPSideloadedKit::class.java) - val kitId = 6000000 - - val configJSONObj = - JSONObject().apply { - put("id", kitId) - } - val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) - `when`(sideloadedKit.configuration).thenReturn(mockedKitConfig) - - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(mockedKitConfig, settingsMap) - - val mockedProvider = MockProvider(mockedKitConfig) - - val options = - MParticleOptions - .builder(MockContext()) - .sideloadedKits(mutableListOf(sideloadedKit) as List) - .build() - val manager: KitManagerImpl = MockKitManagerImpl(options) - val factory = mock(KitIntegrationFactory::class.java) - manager.setKitFactory(factory) - - `when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) - val supportedKit = mutableSetOf(kitId) - `when`(manager.supportedKits).thenReturn(supportedKit) - `when`(sideloadedKit.isDisabled).thenReturn(false) - `when`( - factory.createInstance( - any( - KitManagerImpl::class.java, - ), - any(KitConfiguration::class.java), - ), - ).thenReturn(sideloadedKit) - manager.providers = - ConcurrentHashMap().apply { - put(42, mockedProvider) - } - - val attributes = - hashMapOf( - Pair("test", "Test"), - Pair("lastname", "Test1"), - Pair("number", "(123) 456-9898"), - Pair("customerId", "55555"), - Pair("country", "US"), - ) - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null) - val finalAttributes = mockedProvider.lastAttributes - Assert.assertNotNull(finalAttributes) - finalAttributes!! - Assert.assertEquals(6, finalAttributes.size) - Assert.assertEquals("(123) 456-9898", finalAttributes["number"]) - Assert.assertEquals("55555", finalAttributes["customerId"]) - Assert.assertEquals("Test1", finalAttributes["lastname"]) - Assert.assertEquals("Test", finalAttributes["test"]) - Assert.assertEquals("US", finalAttributes["country"]) - Assert.assertEquals("false", finalAttributes["sandbox"]) - } - - @Test - fun testRokt_selectPlacements_with_PlacementOptions() { - val mockUser = mock(MParticleUser::class.java) - `when`(mockIdentity!!.currentUser).thenReturn(mockUser) - - val manager: KitManagerImpl = MockKitManagerImpl() - val roktListener = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(roktListener.isDisabled).thenReturn(false) - manager.providers = - ConcurrentHashMap().apply { - put(1, roktListener) - } - - val attributes = hashMapOf() - val placementOptions = PlacementOptions(jointSdkSelectPlacements = 123L) - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null, placementOptions) - - val optionsCaptor = ArgumentCaptor.forClass(PlacementOptions::class.java) - verify(roktListener as KitIntegration.RoktListener).selectPlacements( - any(), - any(), - any(), - any(), - any(), - any(), - any(), - optionsCaptor.capture(), - ) - assertSame(placementOptions, optionsCaptor.value) - } - - @Test - fun testRokt_selectPlacements_without_PlacementOptions() { - val mockUser = mock(MParticleUser::class.java) - `when`(mockIdentity!!.currentUser).thenReturn(mockUser) - - val manager: KitManagerImpl = MockKitManagerImpl() - val roktListener = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(roktListener.isDisabled).thenReturn(false) - manager.providers = - ConcurrentHashMap().apply { - put(1, roktListener) - } - - val attributes = hashMapOf() - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null) - - val optionsCaptor = ArgumentCaptor.forClass(PlacementOptions::class.java) - verify(roktListener as KitIntegration.RoktListener).selectPlacements( - any(), - any(), - any(), - any(), - any(), - any(), - any(), - optionsCaptor.capture(), - ) - assertNull(optionsCaptor.value) - } - - @Test - fun testRokt_SandboxMode_When_Environment_IS_Development() { - val sideloadedKit = mock(MPSideloadedKit::class.java) - val kitId = 6000000 - - val configJSONObj = - JSONObject().apply { - put("id", kitId) - } - val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) - - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(mockedKitConfig, settingsMap) - - val mockedProvider = MockProvider(mockedKitConfig) - - val options = - MParticleOptions - .builder(MockContext()) - .sideloadedKits(mutableListOf(sideloadedKit) as List) - .build() - val manager: KitManagerImpl = MockKitManagerImpl(options) - val factory = mock(KitIntegrationFactory::class.java) - manager.setKitFactory(factory) - - `when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) - PowerMockito.mockStatic(MPUtility::class.java) - `when`(MPUtility.isDevEnv()).thenReturn(true) - val supportedKit = mutableSetOf(kitId) - `when`(manager.supportedKits).thenReturn(supportedKit) - `when`(sideloadedKit.isDisabled).thenReturn(false) - `when`( - factory.createInstance( - any( - KitManagerImpl::class.java, - ), - any(KitConfiguration::class.java), - ), - ).thenReturn(sideloadedKit) - manager.providers = - ConcurrentHashMap().apply { - put(42, mockedProvider) - } - - val attributes = - hashMapOf( - Pair("test", "Test"), - Pair("lastname", "Test1"), - Pair("number", "(123) 456-9898"), - Pair("customerId", "55555"), - Pair("country", "US"), - ) - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null) - val finalAttributes = mockedProvider.lastAttributes - Assert.assertNotNull(finalAttributes) - finalAttributes!! - Assert.assertEquals(6, finalAttributes.size) - Assert.assertEquals("(123) 456-9898", finalAttributes["number"]) - Assert.assertEquals("55555", finalAttributes["customerId"]) - Assert.assertEquals("Test1", finalAttributes["lastname"]) - Assert.assertEquals("Test", finalAttributes["test"]) - Assert.assertEquals("US", finalAttributes["country"]) - Assert.assertEquals("true", finalAttributes["sandbox"]) - } - - @Test - fun testRokt_SandboxMode_When_SandBox_is_Pass_In_Attributes_And_Environment_Is_DEV() { - val sideloadedKit = mock(MPSideloadedKit::class.java) - val kitId = 6000000 - - val configJSONObj = - JSONObject().apply { - put("id", kitId) - } - val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) - - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(mockedKitConfig, settingsMap) - - val mockedProvider = MockProvider(mockedKitConfig) - - val options = - MParticleOptions - .builder(MockContext()) - .sideloadedKits(mutableListOf(sideloadedKit) as List) - .build() - val manager: KitManagerImpl = MockKitManagerImpl(options) - val factory = mock(KitIntegrationFactory::class.java) - manager.setKitFactory(factory) - - `when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) - PowerMockito.mockStatic(MPUtility::class.java) - `when`(MPUtility.isDevEnv()).thenReturn(true) - val supportedKit = mutableSetOf(kitId) - `when`(manager.supportedKits).thenReturn(supportedKit) - `when`(sideloadedKit.isDisabled).thenReturn(false) - `when`( - factory.createInstance( - any( - KitManagerImpl::class.java, - ), - any(KitConfiguration::class.java), - ), - ).thenReturn(sideloadedKit) - manager.providers = - ConcurrentHashMap().apply { - put(42, mockedProvider) - } - - val attributes = - hashMapOf( - Pair("test", "Test"), - Pair("lastname", "Test1"), - Pair("number", "(123) 456-9898"), - Pair("customerId", "55555"), - Pair("country", "US"), - Pair("sandbox", "false"), - ) - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - roktApi!!.selectPlacements("Test", attributes, null, null, null, null) - val finalAttributes = mockedProvider.lastAttributes - Assert.assertNotNull(finalAttributes) - finalAttributes!! - Assert.assertEquals(6, finalAttributes.size) - Assert.assertEquals("(123) 456-9898", finalAttributes["number"]) - Assert.assertEquals("55555", finalAttributes["customerId"]) - Assert.assertEquals("Test1", finalAttributes["lastname"]) - Assert.assertEquals("Test", finalAttributes["test"]) - Assert.assertEquals("US", finalAttributes["country"]) - Assert.assertEquals("false", finalAttributes["sandbox"]) - } - @Test fun testSetWrapperSdkVersion() { val manager: KitManagerImpl = MockKitManagerImpl() @@ -1558,308 +947,6 @@ class KitManagerImplTest { .setWrapperSdkVersion(wrapperSdkVersion) } - @Test - fun testEvents_noProviders_returnsEmptyFlow() { - val manager: KitManagerImpl = MockKitManagerImpl() - - val roktApi = manager.getRoktKitApi() - assertNull(roktApi) - val result = roktApi?.events("test-identifier") ?: flowOf() - - runTest { - val elements = result.toList() - assertTrue(elements.isEmpty()) - } - } - - @Test - fun testEvents_providersExistButNotRoktListeners_returnsEmptyFlow() { - val manager: KitManagerImpl = MockKitManagerImpl() - - val nonRoktProvider = mock(KitIntegration::class.java) - `when`(nonRoktProvider.isDisabled).thenReturn(false) - `when`(nonRoktProvider.getName()).thenReturn("NonRoktProvider") - - manager.providers = - ConcurrentHashMap().apply { - put(1, nonRoktProvider) - } - - val roktApi = manager.getRoktKitApi() - assertNull(roktApi) - val result = roktApi?.events("test-identifier") ?: flowOf() - - runTest { - val elements = result.toList() - assertTrue(elements.isEmpty()) - } - } - - @Test - fun testEvents_roktListenerDisabled_returnsEmptyFlow() { - val manager: KitManagerImpl = MockKitManagerImpl() - - val disabledRoktProvider = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(disabledRoktProvider.isDisabled).thenReturn(true) - `when`(disabledRoktProvider.getName()).thenReturn("DisabledRoktProvider") - - manager.providers = - ConcurrentHashMap().apply { - put(1, disabledRoktProvider) - } - - val roktApi = manager.getRoktKitApi() - assertNull(roktApi) - val result = roktApi?.events("test-identifier") ?: flowOf() - - runTest { - val elements = result.toList() - assertTrue(elements.isEmpty()) - } - } - - @Test - fun testEvents_roktListenerEnabled_delegatesToProvider() { - val manager: KitManagerImpl = MockKitManagerImpl() - - val enabledRoktProvider = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(enabledRoktProvider.isDisabled).thenReturn(false) - `when`(enabledRoktProvider.getName()).thenReturn("EnabledRoktProvider") - - val expectedFlow: Flow = flowOf() - val testIdentifier = "test-identifier" - `when`((enabledRoktProvider as KitIntegration.RoktListener).events(testIdentifier)) - .thenReturn(expectedFlow) - - manager.providers = - ConcurrentHashMap().apply { - put(1, enabledRoktProvider) - } - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - val result = roktApi!!.events(testIdentifier) - - verify(enabledRoktProvider as KitIntegration.RoktListener).events(testIdentifier) - assertEquals(expectedFlow, result) - } - - @Test - fun testEvents_multipleProviders_usesFirstEnabledRoktListener() { - val manager: KitManagerImpl = MockKitManagerImpl() - - val nonRoktProvider = mock(KitIntegration::class.java) - `when`(nonRoktProvider.isDisabled).thenReturn(false) - `when`(nonRoktProvider.getName()).thenReturn("NonRoktProvider") - - val disabledRoktProvider = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(disabledRoktProvider.isDisabled).thenReturn(true) - `when`(disabledRoktProvider.getName()).thenReturn("DisabledRoktProvider") - - val enabledRoktProvider = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(enabledRoktProvider.isDisabled).thenReturn(false) - `when`(enabledRoktProvider.getName()).thenReturn("EnabledRoktProvider") - - val secondEnabledRoktProvider = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(secondEnabledRoktProvider.isDisabled).thenReturn(false) - `when`(secondEnabledRoktProvider.getName()).thenReturn("SecondEnabledRoktProvider") - - val expectedFlow: Flow = flowOf() - val testIdentifier = "test-identifier" - `when`((enabledRoktProvider as KitIntegration.RoktListener).events(testIdentifier)) - .thenReturn(expectedFlow) - `when`((secondEnabledRoktProvider as KitIntegration.RoktListener).events(testIdentifier)) - .thenReturn(flowOf()) - - manager.providers = - ConcurrentHashMap().apply { - put(1, nonRoktProvider) - put(2, disabledRoktProvider) - put(3, enabledRoktProvider) - put(4, secondEnabledRoktProvider) - } - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - val result = roktApi!!.events(testIdentifier) - - verify(enabledRoktProvider as KitIntegration.RoktListener).events(testIdentifier) - verify(secondEnabledRoktProvider as KitIntegration.RoktListener, never()).events(any()) - assertEquals(expectedFlow, result) - } - - @Test - fun testEvents_providerThrowsException_returnsEmptyFlow() { - val manager: KitManagerImpl = MockKitManagerImpl() - - val exceptionRoktProvider = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(exceptionRoktProvider.isDisabled).thenReturn(false) - `when`(exceptionRoktProvider.getName()).thenReturn("ExceptionRoktProvider") - `when`((exceptionRoktProvider as KitIntegration.RoktListener).events(any())) - .thenThrow(RuntimeException("Test exception")) - - manager.providers = - ConcurrentHashMap().apply { - put(1, exceptionRoktProvider) - } - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - val result = roktApi!!.events("test-identifier") - - runTest { - val elements = result.toList() - assertTrue(elements.isEmpty()) - } - } - - @Test - fun testEvents_providerThrowsException_returnsEmptyFlowWithoutFallback() { - val manager: KitManagerImpl = MockKitManagerImpl() - - val exceptionRoktProvider = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(exceptionRoktProvider.isDisabled).thenReturn(false) - `when`(exceptionRoktProvider.getName()).thenReturn("ExceptionRoktProvider") - `when`((exceptionRoktProvider as KitIntegration.RoktListener).events(any())) - .thenThrow(RuntimeException("Test exception")) - - val workingRoktProvider = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(workingRoktProvider.isDisabled).thenReturn(false) - `when`(workingRoktProvider.getName()).thenReturn("WorkingRoktProvider") - - val expectedFlow: Flow = flowOf() - val testIdentifier = "test-identifier" - `when`((workingRoktProvider as KitIntegration.RoktListener).events(testIdentifier)) - .thenReturn(expectedFlow) - - manager.providers = - ConcurrentHashMap().apply { - put(1, exceptionRoktProvider) - put(2, workingRoktProvider) - } - - val roktApi = manager.getRoktKitApi() - Assert.assertNotNull(roktApi) - val result = roktApi!!.events(testIdentifier) - - verify(workingRoktProvider as KitIntegration.RoktListener, never()).events(any()) - runTest { - val elements = result.toList() - assertTrue(elements.isEmpty()) - } - } - - internal inner class MockProvider( - val config: KitConfiguration, - ) : KitIntegration(), - KitIntegration.RoktListener { - var lastAttributes: Map? = null - var lastOptions: PlacementOptions? = null - var lastUser: FilteredMParticleUser? = null - - override fun isDisabled(): Boolean = false - - override fun getName(): String = "FakeProvider" - - override fun onKitCreate( - settings: MutableMap?, - context: Context?, - ): MutableList { - TODO("Not yet implemented") - } - - override fun setOptOut(optedOut: Boolean): MutableList { - TODO("Not yet implemented") - } - - override fun getConfiguration(): KitConfiguration = config - - override fun selectPlacements( - viewName: String, - attributes: MutableMap, - mpRoktEventCallback: MpRoktEventCallback?, - placeHolders: MutableMap>?, - fontTypefaces: MutableMap>?, - user: FilteredMParticleUser?, - config: RoktConfig?, - options: PlacementOptions?, - ) { - lastAttributes = attributes.toMap() - lastOptions = options - lastUser = user - Logger.info("selectPlacements with $attributes and options $options") - } - - override fun events(identifier: String): Flow { - Logger.info("events called with identfier: $identifier") - return flowOf() - } - - override fun enrichAttributes( - attributes: MutableMap, - user: FilteredMParticleUser?, - ) { - Logger.info("callRoktComposable with $attributes") - } - - override fun setWrapperSdkVersion(wrapperSdkVersion: WrapperSdkVersion) { - Logger.info("setWrapperSdkVersion with $wrapperSdkVersion") - } - - override fun purchaseFinalized( - placementId: String, - catalogItemId: String, - status: Boolean, - ) { - Logger.info("purchaseFinalized with placementId: $placementId catalogItemId : $catalogItemId status : $status ") - } - - override fun close() { - Logger.info("close called") - } - - override fun setSessionId(sessionId: String) { - Logger.info("setSessionId called with $sessionId") - } - - override fun getSessionId(): String? { - Logger.info("getSessionId called") - return null - } - } - internal inner class KitManagerEventCounter : MockKitManagerImpl() { var logBaseEventCalled = 0 var logCommerceEventCalled = 0 diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/RoktKitApiImplTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/RoktKitApiImplTest.kt deleted file mode 100644 index 65643eba5..000000000 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/RoktKitApiImplTest.kt +++ /dev/null @@ -1,167 +0,0 @@ -package com.mparticle.kits - -import com.mparticle.MParticle -import com.mparticle.identity.IdentityApi -import com.mparticle.internal.MPUtility -import com.mparticle.mock.MockMParticle -import com.mparticle.rokt.PlacementOptions -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.test.runTest -import org.json.JSONObject -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` -import org.mockito.Mockito.withSettings - -class RoktKitApiImplTest { - @Before - fun setUp() { - val identityApi = mock(IdentityApi::class.java) - val instance = MockMParticle() - instance.setIdentityApi(identityApi) - MParticle.setInstance(instance) - } - - @Test - fun testSelectPlacements_mapsAttributesAndAddsSandbox() { - val kitConfig = KitConfiguration.createKitConfiguration(JSONObject().put("id", 42)) - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [ - {"map": "number", "value": "no"}, - {"map": "customerId", "value": "minorcatid"} - ] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(kitConfig, settingsMap) - - val kitIntegration = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(kitIntegration.configuration).thenReturn(kitConfig) - val roktListener = kitIntegration as KitIntegration.RoktListener - val roktApi = RoktKitApiImpl(roktListener, kitIntegration) - - val attributes = - hashMapOf( - "number" to "(123) 456-9898", - "customerId" to "55555", - "country" to "US", - ) - - roktApi.selectPlacements("Test", attributes, null, null, null, null, null) - - @Suppress("UNCHECKED_CAST") - val attributesCaptor = ArgumentCaptor.forClass(Map::class.java) as ArgumentCaptor> - verify(roktListener).selectPlacements( - any(), - attributesCaptor.capture(), - any(), - any(), - any(), - any(), - any(), - any(), - ) - val captured = attributesCaptor.value - assertEquals("(123) 456-9898", captured["no"]) - assertEquals("55555", captured["minorcatid"]) - assertEquals("US", captured["country"]) - assertEquals(MPUtility.isDevEnv().toString(), captured["sandbox"]) - } - - @Test - fun testSelectPlacements_passesPlacementOptions() { - val kitConfig = KitConfiguration.createKitConfiguration(JSONObject().put("id", 42)) - val kitIntegration = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(kitIntegration.configuration).thenReturn(kitConfig) - val roktListener = kitIntegration as KitIntegration.RoktListener - val roktApi = RoktKitApiImpl(roktListener, kitIntegration) - - val placementOptions = PlacementOptions(jointSdkSelectPlacements = 123L) - - roktApi.selectPlacements("Test", emptyMap(), null, null, null, null, placementOptions) - - val optionsCaptor = ArgumentCaptor.forClass(PlacementOptions::class.java) - verify(roktListener).selectPlacements( - any(), - any(), - any(), - any(), - any(), - any(), - any(), - optionsCaptor.capture(), - ) - assertEquals(placementOptions, optionsCaptor.value) - } - - @Test - fun testEvents_returnsEmptyFlowWhenProviderThrows() = runTest { - val kitConfig = KitConfiguration.createKitConfiguration(JSONObject().put("id", 42)) - val kitIntegration = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(kitIntegration.configuration).thenReturn(kitConfig) - val roktListener = kitIntegration as KitIntegration.RoktListener - `when`(roktListener.events(any())).thenThrow(RuntimeException("Test exception")) - val roktApi = RoktKitApiImpl(roktListener, kitIntegration) - - val result = roktApi.events("test-identifier") - - assertTrue(result.toList().isEmpty()) - } - - @Test - fun testPrepareAttributesAsync_delegatesToEnrichAttributes() { - val kitConfig = KitConfiguration.createKitConfiguration(JSONObject().put("id", 42)) - val settingsMap = - hashMapOf( - "placementAttributesMapping" to - """ - [ - {"map": "number", "value": "no"} - ] - """.trimIndent(), - ) - val field = KitConfiguration::class.java.getDeclaredField("settings") - field.isAccessible = true - field.set(kitConfig, settingsMap) - - val kitIntegration = - mock( - KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), - ) - `when`(kitIntegration.configuration).thenReturn(kitConfig) - val roktListener = kitIntegration as KitIntegration.RoktListener - val roktApi = RoktKitApiImpl(roktListener, kitIntegration) - - roktApi.prepareAttributesAsync(mapOf("number" to "(123) 456-9898")) - - @Suppress("UNCHECKED_CAST") - val attributesCaptor = ArgumentCaptor.forClass(Map::class.java) as ArgumentCaptor> - verify(roktListener).enrichAttributes(attributesCaptor.capture(), any()) - val captured = attributesCaptor.value - assertEquals("(123) 456-9898", captured["no"]) - assertEquals(MPUtility.isDevEnv().toString(), captured["sandbox"]) - } -} From 69cf7d425149e4e25d4b3a6ab4e072ce6f5c7b9e Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 07:52:18 -0400 Subject: [PATCH 08/23] refactor: move rokt contract types into rokt kit module --- .../com/mparticle/rokt/PlacementOptions.kt | 3 -- .../com/mparticle/kits/KitIntegration.java | 47 +------------------ .../com/mparticle/kits/KitManagerImplTest.kt | 3 -- .../src/main/kotlin/com/mparticle/Rokt.kt | 9 ++-- .../main/kotlin/com/mparticle/kits/RoktKit.kt | 1 + .../com/mparticle/kits/RoktKitBridge.kt | 35 ++++++++++++++ .../mparticle/kits/RoktKitRequestHelper.kt | 4 +- .../com/mparticle/rokt/PlacementOptions.kt | 6 +++ .../kotlin/com/mparticle/rokt/RoktConfig.kt | 11 ++++- .../com/mparticle/rokt/RoktEmbeddedView.kt | 0 .../rokt/RoktLayoutDimensionCallBack.kt | 0 .../src/test/kotlin/com/mparticle/RoktTest.kt | 9 ++-- 12 files changed, 65 insertions(+), 63 deletions(-) delete mode 100644 android-core/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt create mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt create mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt rename {android-core => kits/rokt/rokt}/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt (71%) rename {android-core => kits/rokt/rokt}/src/main/kotlin/com/mparticle/rokt/RoktEmbeddedView.kt (100%) rename {android-core => kits/rokt/rokt}/src/main/kotlin/com/mparticle/rokt/RoktLayoutDimensionCallBack.kt (100%) diff --git a/android-core/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt b/android-core/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt deleted file mode 100644 index c3f2a3417..000000000 --- a/android-core/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.mparticle.rokt - -data class PlacementOptions(val jointSdkSelectPlacements: Long, val dynamicPerformanceMarkers: Map = mapOf()) diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java index 18633a61c..14aa17d66 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.graphics.Typeface; import android.location.Location; import android.net.Uri; import android.os.Bundle; @@ -15,15 +14,10 @@ import com.mparticle.BaseEvent; import com.mparticle.MPEvent; import com.mparticle.MParticle; -import com.mparticle.MpRoktEventCallback; -import com.mparticle.RoktEvent; import com.mparticle.WrapperSdkVersion; import com.mparticle.commerce.CommerceEvent; import com.mparticle.consent.ConsentState; import com.mparticle.identity.MParticleUser; -import com.mparticle.rokt.PlacementOptions; -import com.mparticle.rokt.RoktConfig; -import com.mparticle.rokt.RoktEmbeddedView; import org.json.JSONObject; @@ -33,8 +27,6 @@ import java.util.List; import java.util.Map; -import kotlinx.coroutines.flow.Flow; - /** * Base Kit implementation - all Kits must subclass this. */ @@ -590,24 +582,10 @@ public interface BatchListener { /** * Interface for Rokt Kit implementations. * - *

This interface is internal to kit-base and is implemented by the Rokt kit to - * handle placement selection, event streaming, and session operations.

+ *

This interface is internal to kit-base and currently used only for wrapper SDK + * version propagation.

*/ public interface RoktListener { - - void selectPlacements(@NonNull String viewName, - @NonNull Map attributes, - @Nullable MpRoktEventCallback mpRoktEventCallback, - @Nullable Map> placeHolders, - @Nullable Map> fontTypefaces, - @Nullable FilteredMParticleUser user, - @Nullable RoktConfig config, - @Nullable PlacementOptions options); - - Flow events(@NonNull String identifier); - - void enrichAttributes( - @NonNull Map attributes, @Nullable FilteredMParticleUser user); /** * Set the SDK version of the mParticle SDK. * This should match the value set in MParticle.getWrapperSdkVersion() @@ -615,26 +593,5 @@ void enrichAttributes( * @param wrapperSdkVersion the version of the mParticle SDK */ void setWrapperSdkVersion(@NonNull WrapperSdkVersion wrapperSdkVersion); - - void purchaseFinalized(@NonNull String placementId, @NonNull String catalogItemId, boolean status); - - void close(); - - /** - * Set the session id to use for the next execute call. - * This is useful for cases where you have a session id from a non-native integration, - * e.g. WebView, and you want the session to be consistent across integrations. - * - * @param sessionId The session id to be set. Must be a non-empty string. - */ - void setSessionId(@NonNull String sessionId); - - /** - * Get the session id to use within a non-native integration e.g. WebView. - * - * @return The session id or null if no session is present. - */ - @Nullable - String getSessionId(); } } diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt index 7e57fd27b..cac9ff304 100644 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt +++ b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt @@ -28,9 +28,6 @@ import com.mparticle.mock.MockContext import com.mparticle.mock.MockKitConfiguration import com.mparticle.mock.MockKitManagerImpl import com.mparticle.mock.MockMParticle -import com.mparticle.rokt.PlacementOptions -import com.mparticle.rokt.RoktConfig -import com.mparticle.rokt.RoktEmbeddedView import com.mparticle.testutils.TestingUtils import junit.framework.TestCase import junit.framework.TestCase.assertEquals diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt index db00354cf..82b09fef3 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt @@ -4,6 +4,7 @@ import android.graphics.Typeface import com.mparticle.internal.KitManager import com.mparticle.internal.Logger import com.mparticle.kits.KitIntegration +import com.mparticle.kits.RoktKitBridge import com.mparticle.kits.RoktKitRequestHelper import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig @@ -110,7 +111,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi * @return The session id or null if no session is present or SDK is not initialized. */ fun getSessionId(): String? = if (isEnabled()) { - resolveRoktKit()?.second?.sessionId + resolveRoktKit()?.second?.getSessionId() } else { null } @@ -134,13 +135,13 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi } } - private fun resolveRoktKit(): Pair? { + private fun resolveRoktKit(): Pair? { if (!mKitManager.isKitActive(MParticle.ServiceProviders.ROKT)) { return null } val kitInstance = mKitManager.getKitInstance(MParticle.ServiceProviders.ROKT) as? KitIntegration ?: return null - val roktListener = kitInstance as? KitIntegration.RoktListener ?: return null - return kitInstance to roktListener + val roktBridge = kitInstance as? RoktKitBridge ?: return null + return kitInstance to roktBridge } private fun isEnabled(): Boolean = try { diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt index eda0dfd82..0b81cc75d 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt @@ -56,6 +56,7 @@ class RoktKit : CommerceListener, IdentityListener, RoktListener, + RoktKitBridge, Rokt.RoktCallback { private var applicationContext: Context? = null private var mpRoktEventCallback: MpRoktEventCallback? = null diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt new file mode 100644 index 000000000..3407471d2 --- /dev/null +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt @@ -0,0 +1,35 @@ +package com.mparticle.kits + +import android.graphics.Typeface +import com.mparticle.MpRoktEventCallback +import com.mparticle.RoktEvent +import com.mparticle.rokt.PlacementOptions +import com.mparticle.rokt.RoktConfig +import com.mparticle.rokt.RoktEmbeddedView +import kotlinx.coroutines.flow.Flow +import java.lang.ref.WeakReference + +internal interface RoktKitBridge { + fun selectPlacements( + viewName: String, + attributes: Map, + mpRoktEventCallback: MpRoktEventCallback?, + placeHolders: MutableMap>?, + fontTypefaces: MutableMap>?, + user: FilteredMParticleUser?, + config: RoktConfig?, + options: PlacementOptions?, + ) + + fun events(identifier: String): Flow + + fun enrichAttributes(attributes: MutableMap, user: FilteredMParticleUser?) + + fun purchaseFinalized(placementId: String, catalogItemId: String, status: Boolean) + + fun close() + + fun setSessionId(sessionId: String) + + fun getSessionId(): String? +} diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt index e1fbeb0ec..940d56e3e 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt @@ -18,7 +18,7 @@ import java.util.Objects internal object RoktKitRequestHelper { fun selectPlacements( kitIntegration: KitIntegration, - roktListener: KitIntegration.RoktListener, + roktListener: RoktKitBridge, viewName: String, attributes: Map, mpRoktEventCallback: MpRoktEventCallback?, @@ -55,7 +55,7 @@ internal object RoktKitRequestHelper { fun prepareAttributesAsync( kitIntegration: KitIntegration, - roktListener: KitIntegration.RoktListener, + roktListener: RoktKitBridge, attributes: Map, ) { val mutableAttributes = attributes.toMutableMap() diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt new file mode 100644 index 000000000..6b0054992 --- /dev/null +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt @@ -0,0 +1,6 @@ +package com.mparticle.rokt + +data class PlacementOptions( + val jointSdkSelectPlacements: Long, + val dynamicPerformanceMarkers: Map = mapOf(), +) diff --git a/android-core/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt similarity index 71% rename from android-core/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt index 28488a0e1..4507df1ea 100644 --- a/android-core/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt @@ -1,6 +1,10 @@ package com.mparticle.rokt -class RoktConfig private constructor(val colorMode: ColorMode?, val cacheConfig: CacheConfig?, val edgeToEdgeDisplay: Boolean) { +class RoktConfig private constructor( + val colorMode: ColorMode?, + val cacheConfig: CacheConfig?, + val edgeToEdgeDisplay: Boolean, +) { data class Builder( private var colorMode: ColorMode? = null, private var cacheConfig: CacheConfig? = null, @@ -18,7 +22,10 @@ class RoktConfig private constructor(val colorMode: ColorMode?, val cacheConfig: enum class ColorMode { LIGHT, DARK, SYSTEM } } -class CacheConfig(val cacheDurationInSeconds: Long = DEFAULT_CACHE_DURATION_SECS, val cacheAttributes: Map? = null) { +class CacheConfig( + val cacheDurationInSeconds: Long = DEFAULT_CACHE_DURATION_SECS, + val cacheAttributes: Map? = null, +) { companion object { const val DEFAULT_CACHE_DURATION_SECS: Long = 90 * 60 } diff --git a/android-core/src/main/kotlin/com/mparticle/rokt/RoktEmbeddedView.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktEmbeddedView.kt similarity index 100% rename from android-core/src/main/kotlin/com/mparticle/rokt/RoktEmbeddedView.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktEmbeddedView.kt diff --git a/android-core/src/main/kotlin/com/mparticle/rokt/RoktLayoutDimensionCallBack.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktLayoutDimensionCallBack.kt similarity index 100% rename from android-core/src/main/kotlin/com/mparticle/rokt/RoktLayoutDimensionCallBack.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktLayoutDimensionCallBack.kt diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt index f37df44ac..f55af6997 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt @@ -7,6 +7,7 @@ import com.mparticle.identity.IdentityApi import com.mparticle.identity.MParticleUser import com.mparticle.internal.KitManager import com.mparticle.kits.KitIntegration +import com.mparticle.kits.RoktKitBridge import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView @@ -49,7 +50,7 @@ class RoktTest { lateinit var mParticleUser: MParticleUser private lateinit var roktKit: KitIntegration - private lateinit var roktListener: KitIntegration.RoktListener + private lateinit var roktListener: RoktKitBridge private lateinit var configManager: FakeConfigManager private lateinit var rokt: Rokt @@ -86,9 +87,9 @@ class RoktTest { roktKit = org.mockito.Mockito.mock( KitIntegration::class.java, - withSettings().extraInterfaces(KitIntegration.RoktListener::class.java), + withSettings().extraInterfaces(RoktKitBridge::class.java), ) - roktListener = roktKit as KitIntegration.RoktListener + roktListener = roktKit as RoktKitBridge MParticle.setInstance(mParticle) `when`(mParticle.Identity()).thenReturn(identityApi) `when`(identityApi.currentUser).thenReturn(mParticleUser) @@ -260,7 +261,7 @@ class RoktTest { @Test fun testGetSessionId_whenEnabled_delegatesToKitManager() { configManager.enabled = true - `when`(roktListener.sessionId).thenReturn("expected-session-id") + `when`(roktListener.getSessionId()).thenReturn("expected-session-id") val result = rokt.getSessionId() verify(roktListener).getSessionId() assertEquals("expected-session-id", result) From b551c58be64de4f1948c58c5cb87e272ca5db1ba Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 07:55:34 -0400 Subject: [PATCH 09/23] refactor: move RoktEvent type into rokt kit module --- .../src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt | 1 - .../rokt/rokt}/src/main/kotlin/com/mparticle/RoktEvent.kt | 0 2 files changed, 1 deletion(-) rename {android-core => kits/rokt/rokt}/src/main/kotlin/com/mparticle/RoktEvent.kt (100%) diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt index cac9ff304..95264c671 100644 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt +++ b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt @@ -9,7 +9,6 @@ import com.mparticle.MPEvent import com.mparticle.MParticle import com.mparticle.MParticleOptions import com.mparticle.MpRoktEventCallback -import com.mparticle.RoktEvent import com.mparticle.WrapperSdk import com.mparticle.WrapperSdkVersion import com.mparticle.commerce.CommerceEvent diff --git a/android-core/src/main/kotlin/com/mparticle/RoktEvent.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/RoktEvent.kt similarity index 100% rename from android-core/src/main/kotlin/com/mparticle/RoktEvent.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/RoktEvent.kt From c35e4f10eb64076c4826bb3a095d6c6bdccfb4f8 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 07:57:22 -0400 Subject: [PATCH 10/23] refactor: move MpRoktEventCallback into rokt kit module --- .../src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt | 1 - .../rokt}/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt | 0 2 files changed, 1 deletion(-) rename {android-core => kits/rokt/rokt}/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt (100%) diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt index 95264c671..f40c62aff 100644 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt +++ b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt @@ -8,7 +8,6 @@ import com.mparticle.BaseEvent import com.mparticle.MPEvent import com.mparticle.MParticle import com.mparticle.MParticleOptions -import com.mparticle.MpRoktEventCallback import com.mparticle.WrapperSdk import com.mparticle.WrapperSdkVersion import com.mparticle.commerce.CommerceEvent diff --git a/android-core/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt similarity index 100% rename from android-core/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt From 81ce58288b17ac4a3b6180e3ca7aa6413edc87a5 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 09:21:24 -0400 Subject: [PATCH 11/23] refactor: use native RoktEvent in rokt kit Remove the mParticle RoktEvent wrapper and return native Rokt SDK events directly to simplify the event pipeline and reduce duplicate mapping logic. --- .../src/main/kotlin/com/mparticle/Rokt.kt | 3 +- .../main/kotlin/com/mparticle/RoktEvent.kt | 102 ------------------ .../main/kotlin/com/mparticle/kits/RoktKit.kt | 42 +------- .../com/mparticle/kits/RoktKitBridge.kt | 2 +- .../src/test/kotlin/com/mparticle/RoktTest.kt | 1 + .../kotlin/com/mparticle/kits/RoktKitTests.kt | 76 ++++++------- 6 files changed, 43 insertions(+), 183 deletions(-) delete mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/RoktEvent.kt diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt index 82b09fef3..36817eab6 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt @@ -9,6 +9,7 @@ import com.mparticle.kits.RoktKitRequestHelper import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import java.lang.ref.WeakReference @@ -59,7 +60,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi * Get a Flow of Rokt events for the specified identifier. * * @param identifier The placement identifier to listen for events - * @return A Flow emitting RoktEvent objects + * @return A Flow emitting native RoktEvent objects */ fun events(identifier: String): Flow = if (isEnabled()) { resolveRoktKit()?.second?.events(identifier) ?: flowOf() diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/RoktEvent.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/RoktEvent.kt deleted file mode 100644 index fa9d6aa85..000000000 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/RoktEvent.kt +++ /dev/null @@ -1,102 +0,0 @@ -package com.mparticle - -// RoktEvent interface for handling events from the Rokt SDK. -sealed interface RoktEvent { - /** - * ShowLoadingIndicator event will be triggered before SDK calls Rokt backend - */ - data object ShowLoadingIndicator : RoktEvent - - /** - * HideLoadingIndicator event will be triggered when SDK obtains a success or failure from - * Rokt backend - */ - data object HideLoadingIndicator : RoktEvent - - /** - * OfferEngagement event will be triggered if User engaged with the offer - * @param placementId - identifier for the placement emitting the event - */ - data class OfferEngagement(val placementId: String) : RoktEvent - - /** - * PositiveEngagement event will be triggered if User positively engaged with the offer - * @param placementId - identifier for the placement emitting the event - */ - data class PositiveEngagement(val placementId: String) : RoktEvent - - /** - * FirstPositiveEngagement event will be triggered when the user positively engaged with the offer first time - * @param placementId - identifier for the placement emitting the event - */ - data class FirstPositiveEngagement(val placementId: String) : RoktEvent - - /** - * PlacementInteractive event will be triggered when placement has been rendered and is interactable - * @param placementId - identifier for the placement emitting the event - */ - data class PlacementInteractive(val placementId: String) : RoktEvent - - /** - * PlacementReady event will be triggered when placement is ready to display but has not rendered content yet - * @param placementId - identifier for the placement emitting the event - */ - data class PlacementReady(val placementId: String) : RoktEvent - - /** - * PlacementClosed event will be triggered when placement closes by user - * @param placementId - identifier for the placement emitting the event - */ - data class PlacementClosed(val placementId: String) : RoktEvent - - /** - * PlacementCompleted event will be triggered when the offer progression moves to the end and no more - * offer to display - * @param placementId - identifier for the placement emitting the event - */ - data class PlacementCompleted(val placementId: String) : RoktEvent - - /** - * PlacementFailure event will be triggered when placement could not be displayed due to some failure - * @param placementId - optional identifier for the placement emitting the event - */ - data class PlacementFailure(val placementId: String? = null) : RoktEvent - - /** - * InitComplete event will be triggered when SDK has finished initialization - * @param success - true if init was successful - */ - data class InitComplete(val success: Boolean) : RoktEvent - - /** - * OpenUrl event will be triggered when user clicks on a link and the link target is set to Passthrough - * @param placementId - identifier for the placement emitting the event - * @param url - url to open - */ - data class OpenUrl(val placementId: String, val url: String) : RoktEvent - - /** - * CartItemInstantPurchase event will be triggered when the catalog item purchase is initiated - * by the user - * @property placementId The layout identifier. - * @property cartItemId The cart item identifier. - * @property catalogItemId The catalog item identifier. - * @property currency The currency used for the purchase. - * @property description The description of the cart item. - * @property linkedProductId The linked product identifier. - * @property totalPrice The total price of the cart item. - * @property quantity The quantity of the cart item. - * @property unitPrice The unit price of the cart item. - */ - data class CartItemInstantPurchase( - val placementId: String, - val cartItemId: String, - val catalogItemId: String, - val currency: String, - val description: String, - val linkedProductId: String, - val totalPrice: Double, - val quantity: Int, - val unitPrice: Double, - ) : RoktEvent -} diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt index 0b81cc75d..d770433f3 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt @@ -37,7 +37,6 @@ import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.lang.ref.WeakReference import java.math.BigDecimal @@ -276,46 +275,7 @@ class RoktKit : return userAttributes } - override fun events(identifier: String): Flow = Rokt.events(identifier).map { event -> - when (event) { - is RoktEvent.HideLoadingIndicator -> com.mparticle.RoktEvent.HideLoadingIndicator - is RoktEvent.ShowLoadingIndicator -> com.mparticle.RoktEvent.ShowLoadingIndicator - is RoktEvent.FirstPositiveEngagement -> com.mparticle.RoktEvent.FirstPositiveEngagement( - event.id, - ) - - is RoktEvent.PositiveEngagement -> com.mparticle.RoktEvent.PositiveEngagement( - event.id, - ) - - is RoktEvent.OfferEngagement -> com.mparticle.RoktEvent.OfferEngagement(event.id) - is RoktEvent.OpenUrl -> com.mparticle.RoktEvent.OpenUrl(event.id, event.url) - is RoktEvent.PlacementClosed -> com.mparticle.RoktEvent.PlacementClosed(event.id) - is RoktEvent.PlacementCompleted -> com.mparticle.RoktEvent.PlacementCompleted( - event.id, - ) - - is RoktEvent.PlacementFailure -> com.mparticle.RoktEvent.PlacementFailure(event.id) - is RoktEvent.PlacementInteractive -> com.mparticle.RoktEvent.PlacementInteractive( - event.id, - ) - - is RoktEvent.PlacementReady -> com.mparticle.RoktEvent.PlacementReady(event.id) - is RoktEvent.CartItemInstantPurchase -> com.mparticle.RoktEvent.CartItemInstantPurchase( - placementId = event.placementId, - cartItemId = event.cartItemId, - catalogItemId = event.catalogItemId, - currency = event.currency, - description = event.description, - linkedProductId = event.linkedProductId, - totalPrice = event.totalPrice, - quantity = event.quantity, - unitPrice = event.unitPrice, - ) - - is RoktEvent.InitComplete -> com.mparticle.RoktEvent.InitComplete(event.success) - } - } + override fun events(identifier: String): Flow = Rokt.events(identifier) override fun setWrapperSdkVersion(wrapperSdkVersion: WrapperSdkVersion) { val sdkFrameworkType = when (wrapperSdkVersion.sdk) { diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt index 3407471d2..b5579517a 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt @@ -2,10 +2,10 @@ package com.mparticle.kits import android.graphics.Typeface import com.mparticle.MpRoktEventCallback -import com.mparticle.RoktEvent import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import java.lang.ref.WeakReference diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt index f55af6997..ed1fb5f01 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt @@ -11,6 +11,7 @@ import com.mparticle.kits.RoktKitBridge import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt index 9717978a6..f79762ca7 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt @@ -761,31 +761,31 @@ class RoktKitTests { } @Test - fun testRoktEventMapping_ShowLoadingIndicator() = runTest { + fun testRoktEvents_ShowLoadingIndicator() = runTest { mockkObject(Rokt) val roktEvent = RoktEvent.ShowLoadingIndicator every { Rokt.events(any()) } returns flowOf(roktEvent) val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.ShowLoadingIndicator) + assertEquals(result, RoktEvent.ShowLoadingIndicator) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_HideLoadingIndicator() = runTest { + fun testRoktEvents_HideLoadingIndicator() = runTest { mockkObject(Rokt) val roktEvent = RoktEvent.HideLoadingIndicator every { Rokt.events(any()) } returns flowOf(roktEvent) val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.HideLoadingIndicator) + assertEquals(result, RoktEvent.HideLoadingIndicator) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_FirstPositiveEngagement() = runTest { + fun testRoktEvents_FirstPositiveEngagement() = runTest { mockkObject(Rokt) val placementId = "test-placement-123" val roktEvent = RoktEvent.FirstPositiveEngagement( @@ -799,12 +799,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.FirstPositiveEngagement(placementId)) + assertEquals(placementId, (result as RoktEvent.FirstPositiveEngagement).id) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_PositiveEngagement() = runTest { + fun testRoktEvents_PositiveEngagement() = runTest { mockkObject(Rokt) val placementId = "test-placement-456" val roktEvent = RoktEvent.PositiveEngagement(placementId) @@ -812,12 +812,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.PositiveEngagement(placementId)) + assertEquals(result, RoktEvent.PositiveEngagement(placementId)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_OfferEngagement() = runTest { + fun testRoktEvents_OfferEngagement() = runTest { mockkObject(Rokt) val placementId = "test-placement-789" val roktEvent = RoktEvent.OfferEngagement(placementId) @@ -825,12 +825,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.OfferEngagement(placementId)) + assertEquals(result, RoktEvent.OfferEngagement(placementId)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_OpenUrl() = runTest { + fun testRoktEvents_OpenUrl() = runTest { mockkObject(Rokt) val placementId = "test-placement-url" val url = "https://example.com" @@ -839,12 +839,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.OpenUrl(placementId, url)) + assertEquals(result, RoktEvent.OpenUrl(placementId, url)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_PlacementClosed() = runTest { + fun testRoktEvents_PlacementClosed() = runTest { mockkObject(Rokt) val placementId = "test-placement-closed" val roktEvent = RoktEvent.PlacementClosed(placementId) @@ -852,12 +852,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.PlacementClosed(placementId)) + assertEquals(result, RoktEvent.PlacementClosed(placementId)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_PlacementCompleted() = runTest { + fun testRoktEvents_PlacementCompleted() = runTest { mockkObject(Rokt) val placementId = "test-placement-completed" val roktEvent = RoktEvent.PlacementCompleted(placementId) @@ -865,12 +865,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.PlacementCompleted(placementId)) + assertEquals(result, RoktEvent.PlacementCompleted(placementId)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_PlacementFailure() = runTest { + fun testRoktEvents_PlacementFailure() = runTest { mockkObject(Rokt) val placementId = "test-placement-failure" val roktEvent = RoktEvent.PlacementFailure(placementId) @@ -878,12 +878,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.PlacementFailure(placementId)) + assertEquals(result, RoktEvent.PlacementFailure(placementId)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_PlacementInteractive() = runTest { + fun testRoktEvents_PlacementInteractive() = runTest { mockkObject(Rokt) val placementId = "test-placement-interactive" val roktEvent = RoktEvent.PlacementInteractive(placementId) @@ -891,12 +891,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.PlacementInteractive(placementId)) + assertEquals(result, RoktEvent.PlacementInteractive(placementId)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_PlacementReady() = runTest { + fun testRoktEvents_PlacementReady() = runTest { mockkObject(Rokt) val placementId = "test-placement-ready" val roktEvent = RoktEvent.PlacementReady(placementId) @@ -904,12 +904,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.PlacementReady(placementId)) + assertEquals(result, RoktEvent.PlacementReady(placementId)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_InitComplete() = runTest { + fun testRoktEvents_InitComplete() = runTest { mockkObject(Rokt) val success = true val roktEvent = RoktEvent.InitComplete(success) @@ -917,12 +917,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.InitComplete(success)) + assertEquals(result, RoktEvent.InitComplete(success)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_CartItemInstantPurchase() = runTest { + fun testRoktEvents_CartItemInstantPurchase() = runTest { mockkObject(Rokt) val roktEvent = RoktEvent.CartItemInstantPurchase( placementId = "test-placement-purchase", @@ -941,7 +941,7 @@ class RoktKitTests { assertEquals( result, - com.mparticle.RoktEvent.CartItemInstantPurchase( + RoktEvent.CartItemInstantPurchase( placementId = "test-placement-purchase", cartItemId = "cart-item-123", catalogItemId = "catalog-item-456", @@ -957,19 +957,19 @@ class RoktKitTests { } @Test - fun testRoktEventMapping_PlacementFailureWithNullId() = runTest { + fun testRoktEvents_PlacementFailureWithNullId() = runTest { mockkObject(Rokt) val roktEvent = RoktEvent.PlacementFailure(null) every { Rokt.events(any()) } returns flowOf(roktEvent) val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.PlacementFailure(null)) + assertEquals(result, RoktEvent.PlacementFailure(null)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_InitCompleteWithFailure() = runTest { + fun testRoktEvents_InitCompleteWithFailure() = runTest { mockkObject(Rokt) val success = false val roktEvent = RoktEvent.InitComplete(success) @@ -977,12 +977,12 @@ class RoktKitTests { val result = roktKit.events("").first() - assertEquals(result, com.mparticle.RoktEvent.InitComplete(success)) + assertEquals(result, RoktEvent.InitComplete(success)) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_MultipleEventsInFlow() = runTest { + fun testRoktEvents_MultipleEventsInFlow() = runTest { mockkObject(Rokt) val events = listOf( RoktEvent.ShowLoadingIndicator, @@ -994,15 +994,15 @@ class RoktKitTests { val results = roktKit.events("").toList() assertEquals(3, results.size) - assertEquals(com.mparticle.RoktEvent.ShowLoadingIndicator, results[0]) - assertEquals(com.mparticle.RoktEvent.HideLoadingIndicator, results[1]) - assertEquals(com.mparticle.RoktEvent.InitComplete(true), results[2]) + assertEquals(RoktEvent.ShowLoadingIndicator, results[0]) + assertEquals(RoktEvent.HideLoadingIndicator, results[1]) + assertEquals(RoktEvent.InitComplete(true), results[2]) unmockkObject(Rokt) } @Test - fun testRoktEventMapping_EmptyFlow() = runTest { + fun testRoktEvents_EmptyFlow() = runTest { mockkObject(Rokt) every { Rokt.events(any()) } returns flowOf() @@ -1013,7 +1013,7 @@ class RoktKitTests { } @Test - fun testRoktEventMapping_WithDifferentIdentifiers() = runTest { + fun testRoktEvents_WithDifferentIdentifiers() = runTest { mockkObject(Rokt) val identifier1 = "test-identifier-1" val identifier2 = "test-identifier-2" @@ -1024,8 +1024,8 @@ class RoktKitTests { val result1 = roktKit.events(identifier1).first() val result2 = roktKit.events(identifier2).first() - assertEquals(com.mparticle.RoktEvent.ShowLoadingIndicator, result1) - assertEquals(com.mparticle.RoktEvent.HideLoadingIndicator, result2) + assertEquals(RoktEvent.ShowLoadingIndicator, result1) + assertEquals(RoktEvent.HideLoadingIndicator, result2) verify { Rokt.events(identifier1) } verify { Rokt.events(identifier2) } From 6e90db2059243dabe6aa38edc2f3c1eb13965f3f Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 09:25:19 -0400 Subject: [PATCH 12/23] refactor: use native unload reasons in rokt callback Remove the mParticle unload reason wrapper and pass Rokt SDK unload reasons through directly to simplify callback handling and eliminate redundant mapping. --- .../com/mparticle/MpRoktEventCallback.kt | 66 +------------------ .../main/kotlin/com/mparticle/kits/RoktKit.kt | 14 +--- .../src/test/kotlin/com/mparticle/RoktTest.kt | 2 +- 3 files changed, 5 insertions(+), 77 deletions(-) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt index 92318642c..b90098709 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt @@ -1,5 +1,7 @@ package com.mparticle +import com.rokt.roktsdk.Rokt + /** * ### Optional callback events for when the view loads and unloads. */ @@ -12,7 +14,7 @@ interface MpRoktEventCallback { /** * onUnLoad Callback will be triggered if the View failed to show or it closed. */ - fun onUnload(reason: UnloadReasons) + fun onUnload(reason: Rokt.UnloadReasons) /** * onShouldShowLoadingIndicator callback will be triggered if View starts processing. @@ -24,65 +26,3 @@ interface MpRoktEventCallback { */ fun onShouldHideLoadingIndicator() } - -/** - * Enum representing the reasons for unloading. - */ -enum class UnloadReasons { - /** - * Called when there are no offers to display so the view does not get loaded in. - */ - NO_OFFERS, - - /** - * View has been rendered and has been completed. - */ - FINISHED, - - /** - * Operation to fetch view took too long to resolve. - */ - TIMEOUT, - - /** - * Some error has occurred regarding the network. - */ - NETWORK_ERROR, - - /** - * View is empty. - */ - NO_WIDGET, - - /** - * Init request was not successful. - */ - INIT_FAILED, - - /** - * Placeholder string mismatch. - */ - UNKNOWN_PLACEHOLDER, - - /** - * Catch-all for all issues. - */ - UNKNOWN, - - ; - - companion object { - /** - * Returns the enum constant matching the provided string. - * If no match is found, UNKNOWN is returned. - * - * @param value the name of the enum constant to look up - * @return the corresponding UnloadReasons constant or UNKNOWN if no match is found - */ - fun from(value: String): UnloadReasons = try { - valueOf(value) - } catch (e: IllegalArgumentException) { - UNKNOWN - } - } -} diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt index d770433f3..b98abd323 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt @@ -11,7 +11,6 @@ import com.mparticle.MPEvent import com.mparticle.MParticle import com.mparticle.MParticle.IdentityType import com.mparticle.MpRoktEventCallback -import com.mparticle.UnloadReasons import com.mparticle.WrapperSdk import com.mparticle.WrapperSdkVersion import com.mparticle.commerce.CommerceEvent @@ -445,18 +444,7 @@ class RoktKit : } override fun onUnload(reason: Rokt.UnloadReasons) { - mpRoktEventCallback?.onUnload( - when (reason) { - Rokt.UnloadReasons.NO_OFFERS -> UnloadReasons.NO_OFFERS - Rokt.UnloadReasons.FINISHED -> UnloadReasons.FINISHED - Rokt.UnloadReasons.TIMEOUT -> UnloadReasons.TIMEOUT - Rokt.UnloadReasons.NETWORK_ERROR -> UnloadReasons.NETWORK_ERROR - Rokt.UnloadReasons.NO_WIDGET -> UnloadReasons.NO_WIDGET - Rokt.UnloadReasons.INIT_FAILED -> UnloadReasons.INIT_FAILED - Rokt.UnloadReasons.UNKNOWN_PLACEHOLDER -> UnloadReasons.UNKNOWN_PLACEHOLDER - Rokt.UnloadReasons.UNKNOWN -> UnloadReasons.UNKNOWN - }, - ) + mpRoktEventCallback?.onUnload(reason) } } diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt index ed1fb5f01..a197b8c82 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt @@ -117,7 +117,7 @@ class RoktTest { println("View loaded") } - override fun onUnload(reason: UnloadReasons) { + override fun onUnload(reason: com.rokt.roktsdk.Rokt.UnloadReasons) { println("View unloaded due to: $reason") } From f9dfa1a5414caa8d2cbde714e7f57027c55c03b2 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 09:28:28 -0400 Subject: [PATCH 13/23] refactor: use native rokt callback types Replace MpRoktEventCallback with the native RoktCallback across the rokt kit and tests to remove callback wrappers and simplify callback delegation. --- .../com/mparticle/MpRoktEventCallback.kt | 28 ------------------- .../src/main/kotlin/com/mparticle/Rokt.kt | 5 ++-- .../main/kotlin/com/mparticle/kits/RoktKit.kt | 19 ++++++------- .../com/mparticle/kits/RoktKitBridge.kt | 4 +-- .../mparticle/kits/RoktKitRequestHelper.kt | 6 ++-- .../kotlin/com/mparticle/kits/RoktLayout.kt | 5 ++-- .../src/test/kotlin/com/mparticle/RoktTest.kt | 2 +- .../kotlin/com/mparticle/kits/RoktKitTests.kt | 14 +++++----- 8 files changed, 27 insertions(+), 56 deletions(-) delete mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt deleted file mode 100644 index b90098709..000000000 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/MpRoktEventCallback.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.mparticle - -import com.rokt.roktsdk.Rokt - -/** - * ### Optional callback events for when the view loads and unloads. - */ -interface MpRoktEventCallback { - /** - * onLoad Callback will be triggered immediately when the View displays. - */ - fun onLoad() - - /** - * onUnLoad Callback will be triggered if the View failed to show or it closed. - */ - fun onUnload(reason: Rokt.UnloadReasons) - - /** - * onShouldShowLoadingIndicator callback will be triggered if View starts processing. - */ - fun onShouldShowLoadingIndicator() - - /** - * onShouldHideLoadingIndicator callback will be triggered if View ends processing. - */ - fun onShouldHideLoadingIndicator() -} diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt index 36817eab6..fe9d4abba 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt @@ -9,6 +9,7 @@ import com.mparticle.kits.RoktKitRequestHelper import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.Rokt.RoktCallback import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf @@ -30,7 +31,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi fun selectPlacements( identifier: String, attributes: Map, - callbacks: MpRoktEventCallback? = null, + callbacks: RoktCallback? = null, embeddedViews: Map>? = null, fontTypefaces: Map>? = null, config: RoktConfig? = null, @@ -44,7 +45,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi roktListener = roktListener, viewName = identifier, attributes = HashMap(attributes), - mpRoktEventCallback = callbacks, + roktCallback = callbacks, placeHolders = embeddedViews, fontTypefaces = fontTypefaces, config = config, diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt index b98abd323..a9692463c 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt @@ -10,7 +10,6 @@ import com.mparticle.BuildConfig import com.mparticle.MPEvent import com.mparticle.MParticle import com.mparticle.MParticle.IdentityType -import com.mparticle.MpRoktEventCallback import com.mparticle.WrapperSdk import com.mparticle.WrapperSdkVersion import com.mparticle.commerce.CommerceEvent @@ -57,7 +56,7 @@ class RoktKit : RoktKitBridge, Rokt.RoktCallback { private var applicationContext: Context? = null - private var mpRoktEventCallback: MpRoktEventCallback? = null + private var roktCallback: RoktCallback? = null private var hashedEmailUserIdentityType: String? = null override fun getName(): String = NAME @@ -182,7 +181,7 @@ class RoktKit : override fun selectPlacements( viewName: String, attributes: Map, - mpRoktEventCallback: MpRoktEventCallback?, + roktCallback: RoktCallback?, placeHolders: MutableMap>?, fontTypefaces: MutableMap>?, filterUser: FilteredMParticleUser?, @@ -209,7 +208,7 @@ class RoktKit : entry.key to WeakReference(widget) }?.toMap() - this.mpRoktEventCallback = mpRoktEventCallback + this.roktCallback = roktCallback val finalAttributes = prepareFinalAttributes(filterUser, attributes) val roktConfig = mpRoktConfig?.toRoktSdkConfig() Rokt.execute( @@ -319,7 +318,7 @@ class RoktKit : suspend fun runComposableWithCallback( attributes: Map, - mpRoktEventCallback: MpRoktEventCallback?, + roktCallback: RoktCallback?, onResult: (Map, RoktCallback) -> Unit, ) { deferredAttributes = CompletableDeferred() @@ -328,7 +327,7 @@ class RoktKit : roktListener = this, attributes = attributes, ) - this.mpRoktEventCallback = mpRoktEventCallback + this.roktCallback = roktCallback CoroutineScope(Dispatchers.Default).launch { val resultAttributes = deferredAttributes!!.await() onResult(resultAttributes, this@RoktKit) @@ -432,19 +431,19 @@ class RoktKit : } override fun onLoad() { - mpRoktEventCallback?.onLoad() + roktCallback?.onLoad() } override fun onShouldHideLoadingIndicator() { - mpRoktEventCallback?.onShouldHideLoadingIndicator() + roktCallback?.onShouldHideLoadingIndicator() } override fun onShouldShowLoadingIndicator() { - mpRoktEventCallback?.onShouldShowLoadingIndicator() + roktCallback?.onShouldShowLoadingIndicator() } override fun onUnload(reason: Rokt.UnloadReasons) { - mpRoktEventCallback?.onUnload(reason) + roktCallback?.onUnload(reason) } } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt index b5579517a..d232d4138 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt @@ -1,10 +1,10 @@ package com.mparticle.kits import android.graphics.Typeface -import com.mparticle.MpRoktEventCallback import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.Rokt.RoktCallback import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import java.lang.ref.WeakReference @@ -13,7 +13,7 @@ internal interface RoktKitBridge { fun selectPlacements( viewName: String, attributes: Map, - mpRoktEventCallback: MpRoktEventCallback?, + roktCallback: RoktCallback?, placeHolders: MutableMap>?, fontTypefaces: MutableMap>?, user: FilteredMParticleUser?, diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt index 940d56e3e..2d5867b42 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt @@ -2,7 +2,6 @@ package com.mparticle.kits import android.graphics.Typeface import com.mparticle.MParticle -import com.mparticle.MpRoktEventCallback import com.mparticle.identity.IdentityApi import com.mparticle.identity.IdentityApiRequest import com.mparticle.identity.MParticleUser @@ -11,6 +10,7 @@ import com.mparticle.internal.MPUtility import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.Rokt.RoktCallback import org.json.JSONException import java.lang.ref.WeakReference import java.util.Objects @@ -21,7 +21,7 @@ internal object RoktKitRequestHelper { roktListener: RoktKitBridge, viewName: String, attributes: Map, - mpRoktEventCallback: MpRoktEventCallback?, + roktCallback: RoktCallback?, placeHolders: Map>?, fontTypefaces: Map>?, config: RoktConfig?, @@ -43,7 +43,7 @@ internal object RoktKitRequestHelper { roktListener.selectPlacements( viewName, finalAttributes, - mpRoktEventCallback, + roktCallback, placeHolders?.toMutableMap(), fontTypefaces?.toMutableMap(), FilteredMParticleUser.getInstance(user?.id ?: 0L, kitIntegration), diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt index 0987ea40d..7aca02d5c 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt @@ -5,7 +5,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import com.mparticle.MpRoktEventCallback import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.rokt.roktsdk.Rokt @@ -18,7 +17,7 @@ fun RoktLayout( attributes: Map, location: String, modifier: Modifier = Modifier, - mpRoktEventCallback: MpRoktEventCallback? = null, + roktCallback: Rokt.RoktCallback? = null, config: RoktConfig? = null, ) { var placementOptions: PlacementOptions? = null @@ -33,7 +32,7 @@ fun RoktLayout( LaunchedEffect(Unit) { instance?.runComposableWithCallback( HashMap(attributes), - mpRoktEventCallback, + roktCallback, { resultMap, callback -> resultMapState.value = RoktResult(resultMap, callback) }, diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt index a197b8c82..d1a5cf3b7 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt @@ -112,7 +112,7 @@ class RoktTest { val config = RoktConfig.Builder().colorMode(RoktConfig.ColorMode.DARK).build() val callbacks = - object : MpRoktEventCallback { + object : com.rokt.roktsdk.Rokt.RoktCallback { override fun onLoad() { println("View loaded") } diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt index f79762ca7..89afdaeee 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt @@ -160,7 +160,7 @@ class RoktKitTests { roktKit.selectPlacements( viewName = "test_view", attributes = inputAttributes, - mpRoktEventCallback = null, + roktCallback = null, placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, @@ -217,7 +217,7 @@ class RoktKitTests { roktKit.selectPlacements( viewName = "test_view", attributes = emptyMap(), - mpRoktEventCallback = null, + roktCallback = null, placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, @@ -266,7 +266,7 @@ class RoktKitTests { roktKit.selectPlacements( viewName = "test_view", attributes = emptyMap(), - mpRoktEventCallback = null, + roktCallback = null, placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, @@ -315,7 +315,7 @@ class RoktKitTests { roktKit.selectPlacements( viewName = "test_view", attributes = emptyMap(), - mpRoktEventCallback = null, + roktCallback = null, placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, @@ -382,7 +382,7 @@ class RoktKitTests { roktKit.selectPlacements( viewName = "test", attributes = inputAttributes, - mpRoktEventCallback = null, + roktCallback = null, placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, @@ -457,7 +457,7 @@ class RoktKitTests { roktKit.selectPlacements( viewName = "test", attributes = inputAttributes, - mpRoktEventCallback = null, + roktCallback = null, placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, @@ -1358,7 +1358,7 @@ class RoktKitTests { roktKit.selectPlacements( viewName = "test_view", attributes = testAttributes, - mpRoktEventCallback = null, + roktCallback = null, placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, From 4015884c1e812f3a3f5913d38d64cad0f78d644f Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 09:33:02 -0400 Subject: [PATCH 14/23] refactor: use native placement options type Replace the local PlacementOptions wrapper with the native Rokt SDK PlacementOptions across the rokt kit and remove the now-redundant conversion layer. --- kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt | 3 ++- .../main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt | 6 ------ .../rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt | 4 ++-- .../src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt | 2 +- .../main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt | 2 +- .../rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt | 4 ++-- .../src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt | 6 ------ kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt | 2 +- 8 files changed, 9 insertions(+), 20 deletions(-) delete mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt index fe9d4abba..eaa5e7d81 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt @@ -6,9 +6,9 @@ import com.mparticle.internal.Logger import com.mparticle.kits.KitIntegration import com.mparticle.kits.RoktKitBridge import com.mparticle.kits.RoktKitRequestHelper -import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow @@ -155,5 +155,6 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi private fun buildPlacementOptions(): PlacementOptions = PlacementOptions( jointSdkSelectPlacements = System.currentTimeMillis(), + dynamicPerformanceMarkers = mapOf(), ) } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt index 45a6ee4f8..a5492a47a 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt @@ -1,6 +1,5 @@ package com.mparticle.kits -import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.rokt.roktsdk.CacheConfig import com.mparticle.rokt.CacheConfig as MpCacheConfig @@ -31,8 +30,3 @@ fun RoktConfig.toRoktSdkConfig(): RoktSdkConfig { return builder.build() } - -fun PlacementOptions.toRoktSdkPlacementOptions(): com.rokt.roktsdk.PlacementOptions = com.rokt.roktsdk.PlacementOptions( - jointSdkSelectPlacements = this.jointSdkSelectPlacements, - dynamicPerformanceMarkers = this.dynamicPerformanceMarkers, -) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt index a9692463c..146a75746 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt @@ -18,9 +18,9 @@ import com.mparticle.internal.Logger import com.mparticle.kits.KitIntegration.CommerceListener import com.mparticle.kits.KitIntegration.IdentityListener import com.mparticle.kits.KitIntegration.RoktListener -import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt import com.rokt.roktsdk.Rokt.RoktCallback import com.rokt.roktsdk.Rokt.SdkFrameworkType.Android @@ -219,7 +219,7 @@ class RoktKit : placeholders.takeIf { it?.isNotEmpty() == true }, fontTypefaces.takeIf { it?.isNotEmpty() == true }, roktConfig, - placementOptions?.toRoktSdkPlacementOptions(), + placementOptions, ) } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt index d232d4138..93d31db0d 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt @@ -1,9 +1,9 @@ package com.mparticle.kits import android.graphics.Typeface -import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt index 2d5867b42..358b089d6 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt @@ -7,9 +7,9 @@ import com.mparticle.identity.IdentityApiRequest import com.mparticle.identity.MParticleUser import com.mparticle.internal.Logger import com.mparticle.internal.MPUtility -import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback import org.json.JSONException import java.lang.ref.WeakReference diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt index 7aca02d5c..835a95cd2 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt @@ -5,8 +5,8 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig +import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt @Composable @@ -48,7 +48,7 @@ fun RoktLayout( onShouldHideLoadingIndicator = { resultMap.callback.onShouldHideLoadingIndicator() }, onUnload = { reason -> resultMap.callback.onUnload(reason) }, config = config?.toRoktSdkConfig(), - placementOptions = placementOptions?.toRoktSdkPlacementOptions(), + placementOptions = placementOptions, ) } } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt deleted file mode 100644 index 6b0054992..000000000 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/PlacementOptions.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.mparticle.rokt - -data class PlacementOptions( - val jointSdkSelectPlacements: Long, - val dynamicPerformanceMarkers: Map = mapOf(), -) diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt index d1a5cf3b7..694303a88 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt @@ -8,9 +8,9 @@ import com.mparticle.identity.MParticleUser import com.mparticle.internal.KitManager import com.mparticle.kits.KitIntegration import com.mparticle.kits.RoktKitBridge -import com.mparticle.rokt.PlacementOptions import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView +import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf From e110e9aba0d61f227dec55e2aea0f3d871a95132 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 09:39:35 -0400 Subject: [PATCH 15/23] refactor: use native rokt config types Replace mParticle RoktConfig and CacheConfig wrappers with native Rokt SDK config types and remove the now-unnecessary config conversion layer and tests. --- .../src/main/kotlin/com/mparticle/Rokt.kt | 2 +- .../mparticle/kits/RoktConfigExtensions.kt | 32 --------- .../main/kotlin/com/mparticle/kits/RoktKit.kt | 5 +- .../com/mparticle/kits/RoktKitBridge.kt | 2 +- .../mparticle/kits/RoktKitRequestHelper.kt | 2 +- .../kotlin/com/mparticle/kits/RoktLayout.kt | 4 +- .../kotlin/com/mparticle/rokt/RoktConfig.kt | 32 --------- .../src/test/kotlin/com/mparticle/RoktTest.kt | 2 +- .../kits/RoktConfigExtensionsTest.kt | 67 ------------------- .../kotlin/com/mparticle/kits/RoktKitTests.kt | 14 ++-- 10 files changed, 15 insertions(+), 147 deletions(-) delete mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt delete mode 100644 kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt delete mode 100644 kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktConfigExtensionsTest.kt diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt index eaa5e7d81..57c437dc0 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt @@ -6,10 +6,10 @@ import com.mparticle.internal.Logger import com.mparticle.kits.KitIntegration import com.mparticle.kits.RoktKitBridge import com.mparticle.kits.RoktKitRequestHelper -import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback +import com.rokt.roktsdk.RoktConfig import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt deleted file mode 100644 index a5492a47a..000000000 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktConfigExtensions.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.mparticle.kits - -import com.mparticle.rokt.RoktConfig -import com.rokt.roktsdk.CacheConfig -import com.mparticle.rokt.CacheConfig as MpCacheConfig -import com.rokt.roktsdk.RoktConfig as RoktSdkConfig - -fun MpCacheConfig.toRoktSdkCacheConfig(): CacheConfig = CacheConfig( - cacheDurationInSeconds = this.cacheDurationInSeconds, - cacheAttributes = this.cacheAttributes, -) - -fun RoktConfig.toRoktSdkConfig(): RoktSdkConfig { - val colorMode = when (this.colorMode) { - RoktConfig.ColorMode.LIGHT -> RoktSdkConfig.ColorMode.LIGHT - RoktConfig.ColorMode.DARK -> RoktSdkConfig.ColorMode.DARK - RoktConfig.ColorMode.SYSTEM -> RoktSdkConfig.ColorMode.SYSTEM - else -> RoktSdkConfig.ColorMode.SYSTEM - } - - val cacheConfig = this.cacheConfig?.toRoktSdkCacheConfig() - - val edgeToEdgeDisplay = this.edgeToEdgeDisplay - - val builder = RoktSdkConfig.Builder() - .colorMode(colorMode) - .edgeToEdgeDisplay(edgeToEdgeDisplay) - - cacheConfig?.let { builder.cacheConfig(it) } - - return builder.build() -} diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt index 146a75746..021042f33 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt @@ -18,7 +18,6 @@ import com.mparticle.internal.Logger import com.mparticle.kits.KitIntegration.CommerceListener import com.mparticle.kits.KitIntegration.IdentityListener import com.mparticle.kits.KitIntegration.RoktListener -import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt @@ -27,6 +26,7 @@ import com.rokt.roktsdk.Rokt.SdkFrameworkType.Android import com.rokt.roktsdk.Rokt.SdkFrameworkType.Cordova import com.rokt.roktsdk.Rokt.SdkFrameworkType.Flutter import com.rokt.roktsdk.Rokt.SdkFrameworkType.ReactNative +import com.rokt.roktsdk.RoktConfig import com.rokt.roktsdk.RoktEvent import com.rokt.roktsdk.RoktWidgetDimensionCallBack import com.rokt.roktsdk.Widget @@ -185,7 +185,7 @@ class RoktKit : placeHolders: MutableMap>?, fontTypefaces: MutableMap>?, filterUser: FilteredMParticleUser?, - mpRoktConfig: RoktConfig?, + roktConfig: RoktConfig?, placementOptions: PlacementOptions?, ) { val placeholders: Map>? = placeHolders?.mapNotNull { entry -> @@ -210,7 +210,6 @@ class RoktKit : this.roktCallback = roktCallback val finalAttributes = prepareFinalAttributes(filterUser, attributes) - val roktConfig = mpRoktConfig?.toRoktSdkConfig() Rokt.execute( viewName, finalAttributes, diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt index 93d31db0d..45c816eca 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt @@ -1,10 +1,10 @@ package com.mparticle.kits import android.graphics.Typeface -import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback +import com.rokt.roktsdk.RoktConfig import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import java.lang.ref.WeakReference diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt index 358b089d6..797a812f4 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt @@ -7,10 +7,10 @@ import com.mparticle.identity.IdentityApiRequest import com.mparticle.identity.MParticleUser import com.mparticle.internal.Logger import com.mparticle.internal.MPUtility -import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback +import com.rokt.roktsdk.RoktConfig import org.json.JSONException import java.lang.ref.WeakReference import java.util.Objects diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt index 835a95cd2..6469aea2c 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt @@ -5,9 +5,9 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import com.mparticle.rokt.RoktConfig import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt +import com.rokt.roktsdk.RoktConfig @Composable @Suppress("FunctionName") @@ -47,7 +47,7 @@ fun RoktLayout( onShouldShowLoadingIndicator = { resultMap.callback.onShouldShowLoadingIndicator() }, onShouldHideLoadingIndicator = { resultMap.callback.onShouldHideLoadingIndicator() }, onUnload = { reason -> resultMap.callback.onUnload(reason) }, - config = config?.toRoktSdkConfig(), + config = config, placementOptions = placementOptions, ) } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt deleted file mode 100644 index 4507df1ea..000000000 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktConfig.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.mparticle.rokt - -class RoktConfig private constructor( - val colorMode: ColorMode?, - val cacheConfig: CacheConfig?, - val edgeToEdgeDisplay: Boolean, -) { - data class Builder( - private var colorMode: ColorMode? = null, - private var cacheConfig: CacheConfig? = null, - private var edgeToEdgeDisplay: Boolean = true, - ) { - fun colorMode(mode: ColorMode) = apply { this.colorMode = mode } - - fun cacheConfig(cacheConfig: CacheConfig) = apply { this.cacheConfig = cacheConfig } - - fun edgeToEdgeDisplay(edgeToEdgeDisplay: Boolean) = apply { this.edgeToEdgeDisplay = edgeToEdgeDisplay } - - fun build(): RoktConfig = RoktConfig(colorMode, cacheConfig, edgeToEdgeDisplay) - } - - enum class ColorMode { LIGHT, DARK, SYSTEM } -} - -class CacheConfig( - val cacheDurationInSeconds: Long = DEFAULT_CACHE_DURATION_SECS, - val cacheAttributes: Map? = null, -) { - companion object { - const val DEFAULT_CACHE_DURATION_SECS: Long = 90 * 60 - } -} diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt index 694303a88..ef8c3dd25 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt @@ -8,9 +8,9 @@ import com.mparticle.identity.MParticleUser import com.mparticle.internal.KitManager import com.mparticle.kits.KitIntegration import com.mparticle.kits.RoktKitBridge -import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions +import com.rokt.roktsdk.RoktConfig import com.rokt.roktsdk.RoktEvent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktConfigExtensionsTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktConfigExtensionsTest.kt deleted file mode 100644 index 14a407c99..000000000 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktConfigExtensionsTest.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.mparticle.kits - -import com.mparticle.rokt.CacheConfig -import com.mparticle.rokt.RoktConfig -import io.mockk.every -import io.mockk.mockk -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNotNull -import org.junit.Test -import com.rokt.roktsdk.RoktConfig as SdkRoktConfig - -class RoktConfigExtensionsTest { - - @Test - fun `toRoktSdkConfig maps color mode and edgeToEdge`() { - val source = mockk() - every { source.colorMode } returns RoktConfig.ColorMode.DARK - every { source.edgeToEdgeDisplay } returns true - every { source.cacheConfig } returns null - - val result: SdkRoktConfig = source.toRoktSdkConfig() - - assertEquals(SdkRoktConfig.ColorMode.DARK, result.colorMode) - assertEquals(true, result.edgeToEdgeDisplay) - } - - @Test - fun `toRoktSdkConfig maps cacheConfig when present`() { - val cacheAttributes = mapOf( - "key1" to "value1", - "key2" to "value2", - ) - - val cacheConfig = mockk() - every { cacheConfig.cacheDurationInSeconds } returns 3600 - every { cacheConfig.cacheAttributes } returns cacheAttributes - - val source = mockk() - every { source.colorMode } returns RoktConfig.ColorMode.LIGHT - every { source.edgeToEdgeDisplay } returns false - every { source.cacheConfig } returns cacheConfig - - val result: SdkRoktConfig = source.toRoktSdkConfig() - - assertEquals(SdkRoktConfig.ColorMode.LIGHT, result.colorMode) - assertEquals(false, result.edgeToEdgeDisplay) - assertNotNull(result.cacheConfig) - assertEquals(3600L, result.cacheConfig?.cacheDurationInSeconds) - assertEquals(cacheAttributes, result.cacheConfig?.cacheAttributes) - } - - @Test - fun `toRoktSdkCacheConfig maps fields`() { - val cacheAttributes = mapOf( - "a" to "1", - "b" to "2", - ) - val mpCache = mockk() - every { mpCache.cacheDurationInSeconds } returns 120 - every { mpCache.cacheAttributes } returns cacheAttributes - - val sdkCache = mpCache.toRoktSdkCacheConfig() - - assertEquals(120, sdkCache.cacheDurationInSeconds) - assertEquals(cacheAttributes, sdkCache.cacheAttributes) - } -} diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt index 89afdaeee..ec8cbbffd 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktKitTests.kt @@ -164,7 +164,7 @@ class RoktKitTests { placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, - mpRoktConfig = null, + roktConfig = null, placementOptions = null, ) @@ -221,7 +221,7 @@ class RoktKitTests { placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, - mpRoktConfig = null, + roktConfig = null, placementOptions = null, ) @@ -270,7 +270,7 @@ class RoktKitTests { placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, - mpRoktConfig = null, + roktConfig = null, placementOptions = null, ) @@ -319,7 +319,7 @@ class RoktKitTests { placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, - mpRoktConfig = null, + roktConfig = null, placementOptions = null, ) @@ -386,7 +386,7 @@ class RoktKitTests { placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, - mpRoktConfig = null, + roktConfig = null, placementOptions = null, ) @@ -461,7 +461,7 @@ class RoktKitTests { placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, - mpRoktConfig = null, + roktConfig = null, placementOptions = null, ) @@ -1362,7 +1362,7 @@ class RoktKitTests { placeHolders = null, fontTypefaces = null, filterUser = mockFilterUser, - mpRoktConfig = null, + roktConfig = null, placementOptions = null, ) From 3ab08705329b8aaf9a62dd56b37ac3f9f29024ef Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 09:45:47 -0400 Subject: [PATCH 16/23] refactor: move rokt facade and embedded view types to kits package Relocate Rokt, RoktEmbeddedView, RoktLayoutDimensionCallBack, and RoktTest into com.mparticle.kits to keep kit-owned types co-located and simplify package boundaries. --- .../mparticle/kits/MParticleRoktExtensions.kt | 2 +- .../kotlin/com/mparticle/{ => kits}/Rokt.kt | 54 +------------------ .../{rokt => kits}/RoktEmbeddedView.kt | 2 +- .../main/kotlin/com/mparticle/kits/RoktKit.kt | 1 - .../com/mparticle/kits/RoktKitBridge.kt | 1 - .../mparticle/kits/RoktKitRequestHelper.kt | 1 - .../RoktLayoutDimensionCallBack.kt | 2 +- .../com/mparticle/{ => kits}/RoktTest.kt | 6 +-- 8 files changed, 7 insertions(+), 62 deletions(-) rename kits/rokt/rokt/src/main/kotlin/com/mparticle/{ => kits}/Rokt.kt (66%) rename kits/rokt/rokt/src/main/kotlin/com/mparticle/{rokt => kits}/RoktEmbeddedView.kt (94%) rename kits/rokt/rokt/src/main/kotlin/com/mparticle/{rokt => kits}/RoktLayoutDimensionCallBack.kt (84%) rename kits/rokt/rokt/src/test/kotlin/com/mparticle/{ => kits}/RoktTest.kt (98%) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt index 508f437ca..8bce6a41a 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt @@ -1,7 +1,7 @@ package com.mparticle.rokt import com.mparticle.MParticle -import com.mparticle.Rokt +import com.mparticle.kits.Rokt /** * Kotlin-friendly accessor for the legacy Rokt API object. diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt similarity index 66% rename from kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt index 57c437dc0..c6232d82a 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt @@ -1,12 +1,9 @@ -package com.mparticle +package com.mparticle.kits import android.graphics.Typeface +import com.mparticle.MParticle import com.mparticle.internal.KitManager import com.mparticle.internal.Logger -import com.mparticle.kits.KitIntegration -import com.mparticle.kits.RoktKitBridge -import com.mparticle.kits.RoktKitRequestHelper -import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback import com.rokt.roktsdk.RoktConfig @@ -16,17 +13,6 @@ import kotlinx.coroutines.flow.flowOf import java.lang.ref.WeakReference class Rokt internal constructor(private val mConfigManager: Any, private val mKitManager: KitManager) { - - /** - * Display a Rokt placement with the specified parameters. - * - * @param identifier The placement identifier - * @param attributes User attributes to pass to Rokt - * @param callbacks Optional callback for Rokt events - * @param embeddedViews Optional map of embedded view placeholders - * @param fontTypefaces Optional map of font typefaces - * @param config Optional Rokt configuration - */ @JvmOverloads fun selectPlacements( identifier: String, @@ -57,72 +43,36 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi } } - /** - * Get a Flow of Rokt events for the specified identifier. - * - * @param identifier The placement identifier to listen for events - * @return A Flow emitting native RoktEvent objects - */ fun events(identifier: String): Flow = if (isEnabled()) { resolveRoktKit()?.second?.events(identifier) ?: flowOf() } else { flowOf() } - /** - * Notify Rokt that a purchase has been finalized. - * - * @param placementId The placement identifier - * @param catalogItemId The catalog item identifier - * @param status Whether the purchase was successful - */ fun purchaseFinalized(placementId: String, catalogItemId: String, status: Boolean) { if (isEnabled()) { resolveRoktKit()?.second?.purchaseFinalized(placementId, catalogItemId, status) } } - /** - * Close any active Rokt placements. - */ fun close() { if (isEnabled()) { resolveRoktKit()?.second?.close() } } - /** - * Set the session id to use for the next execute call. - * - * This is useful for cases where you have a session id from a non-native integration, - * e.g. WebView, and you want the session to be consistent across integrations. - * - * **Note:** Empty strings are ignored and will not update the session. - * - * @param sessionId The session id to be set. Must be a non-empty string. - */ fun setSessionId(sessionId: String) { if (isEnabled()) { resolveRoktKit()?.second?.setSessionId(sessionId) } } - /** - * Get the session id to use within a non-native integration e.g. WebView. - * - * @return The session id or null if no session is present or SDK is not initialized. - */ fun getSessionId(): String? = if (isEnabled()) { resolveRoktKit()?.second?.getSessionId() } else { null } - /** - * Prepare attributes asynchronously before executing a placement. - * - * @param attributes The attributes to prepare - */ fun prepareAttributesAsync(attributes: Map) { if (isEnabled()) { val resolved = resolveRoktKit() diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktEmbeddedView.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktEmbeddedView.kt similarity index 94% rename from kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktEmbeddedView.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktEmbeddedView.kt index 03db66366..59da59aae 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktEmbeddedView.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktEmbeddedView.kt @@ -1,4 +1,4 @@ -package com.mparticle.rokt +package com.mparticle.kits import android.content.Context import android.util.AttributeSet diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt index 021042f33..115c1dcc7 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKit.kt @@ -18,7 +18,6 @@ import com.mparticle.internal.Logger import com.mparticle.kits.KitIntegration.CommerceListener import com.mparticle.kits.KitIntegration.IdentityListener import com.mparticle.kits.KitIntegration.RoktListener -import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt import com.rokt.roktsdk.Rokt.RoktCallback diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt index 45c816eca..00e99d555 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitBridge.kt @@ -1,7 +1,6 @@ package com.mparticle.kits import android.graphics.Typeface -import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback import com.rokt.roktsdk.RoktConfig diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt index 797a812f4..a808ebe89 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt @@ -7,7 +7,6 @@ import com.mparticle.identity.IdentityApiRequest import com.mparticle.identity.MParticleUser import com.mparticle.internal.Logger import com.mparticle.internal.MPUtility -import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.Rokt.RoktCallback import com.rokt.roktsdk.RoktConfig diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktLayoutDimensionCallBack.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayoutDimensionCallBack.kt similarity index 84% rename from kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktLayoutDimensionCallBack.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayoutDimensionCallBack.kt index e22535c90..5dffc4f67 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/rokt/RoktLayoutDimensionCallBack.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayoutDimensionCallBack.kt @@ -1,4 +1,4 @@ -package com.mparticle.rokt +package com.mparticle.kits interface RoktLayoutDimensionCallBack { fun onHeightChanged(height: Int) diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt similarity index 98% rename from kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt rename to kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt index ef8c3dd25..71900e90a 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt @@ -1,14 +1,12 @@ -package com.mparticle +package com.mparticle.kits import android.graphics.Typeface import android.os.Looper import android.os.SystemClock +import com.mparticle.MParticle import com.mparticle.identity.IdentityApi import com.mparticle.identity.MParticleUser import com.mparticle.internal.KitManager -import com.mparticle.kits.KitIntegration -import com.mparticle.kits.RoktKitBridge -import com.mparticle.rokt.RoktEmbeddedView import com.rokt.roktsdk.PlacementOptions import com.rokt.roktsdk.RoktConfig import com.rokt.roktsdk.RoktEvent From dedb2cd61b8ad623366d13e596275718640e655f Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 11:50:46 -0400 Subject: [PATCH 17/23] refactor: replace rokt isEnabled reflection with callback provider Avoid runtime method lookup for isEnabled by injecting an explicit enablement callback and wiring it to core callbacks with a safe opt-out fallback, preserving behavior under obfuscation. --- .../kotlin/com/mparticle/kits/MParticleRoktExtensions.kt | 5 ++++- .../rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt | 9 ++------- .../rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt index 8bce6a41a..9bba051eb 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt @@ -1,6 +1,7 @@ package com.mparticle.rokt import com.mparticle.MParticle +import com.mparticle.internal.CoreCallbacks import com.mparticle.kits.Rokt /** @@ -22,5 +23,7 @@ object MParticleRokt { private fun createRokt(mParticle: MParticle): Rokt { val configManager = mParticle.Internal().configManager val kitManager = mParticle.Internal().kitManager - return Rokt(configManager, kitManager) + return Rokt(kitManager) { + (configManager as? CoreCallbacks)?.isEnabled() ?: (mParticle.getOptOut() != true) + } } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt index c6232d82a..c7bc4bc78 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import java.lang.ref.WeakReference -class Rokt internal constructor(private val mConfigManager: Any, private val mKitManager: KitManager) { +class Rokt internal constructor(private val mKitManager: KitManager, private val isEnabledProvider: () -> Boolean) { @JvmOverloads fun selectPlacements( identifier: String, @@ -96,12 +96,7 @@ class Rokt internal constructor(private val mConfigManager: Any, private val mKi return kitInstance to roktBridge } - private fun isEnabled(): Boolean = try { - val field = mConfigManager.javaClass.getMethod("isEnabled") - field.invoke(mConfigManager) as? Boolean ?: false - } catch (ignored: Exception) { - false - } + private fun isEnabled(): Boolean = isEnabledProvider() private fun buildPlacementOptions(): PlacementOptions = PlacementOptions( jointSdkSelectPlacements = System.currentTimeMillis(), diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt index 71900e90a..fdc23686f 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt @@ -94,7 +94,7 @@ class RoktTest { `when`(identityApi.currentUser).thenReturn(mParticleUser) `when`(kitManager.isKitActive(MParticle.ServiceProviders.ROKT)).thenReturn(true) `when`(kitManager.getKitInstance(MParticle.ServiceProviders.ROKT)).thenReturn(roktKit) - rokt = Rokt(configManager, kitManager) + rokt = Rokt(kitManager) { configManager.enabled } } @Test From 6e2bb9a1d89a77b304aa31b01bffb4f7e01d327d Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 13:17:00 -0400 Subject: [PATCH 18/23] refactor: route Rokt enabled check through kit manager contract Add isEnabled to KitManager and use it from the Rokt facade so enablement is resolved through a stable kit-layer API without reflection or config manager coupling. --- .../java/com/mparticle/internal/KitFrameworkWrapper.java | 5 +++++ .../src/main/java/com/mparticle/internal/KitManager.java | 2 ++ .../src/main/java/com/mparticle/kits/KitManagerImpl.java | 5 +++++ .../kotlin/com/mparticle/kits/MParticleRoktExtensions.kt | 6 +----- kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt | 4 ++-- .../rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt | 3 ++- 6 files changed, 17 insertions(+), 8 deletions(-) diff --git a/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java b/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java index 6dd1b4768..08be54515 100644 --- a/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java +++ b/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java @@ -457,6 +457,11 @@ public boolean onPushRegistration(String instanceId, String senderId) { return false; } + @Override + public boolean isEnabled() { + return mCoreCallbacks.isEnabled(); + } + @Override public boolean isKitActive(int kitId) { if (mKitManager != null) { diff --git a/android-core/src/main/java/com/mparticle/internal/KitManager.java b/android-core/src/main/java/com/mparticle/internal/KitManager.java index 28009fa15..94b50fdbd 100644 --- a/android-core/src/main/java/com/mparticle/internal/KitManager.java +++ b/android-core/src/main/java/com/mparticle/internal/KitManager.java @@ -75,6 +75,8 @@ public interface KitManager { boolean onPushRegistration(String instanceId, String senderId); + boolean isEnabled(); + boolean isKitActive(int kitId); Object getKitInstance(int kitId); diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java index fb4662eb5..4d1400c03 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java @@ -156,6 +156,11 @@ public int getUserBucket() { return mCoreCallbacks.getUserBucket(); } + @Override + public boolean isEnabled() { + return mCoreCallbacks.isEnabled(); + } + public boolean isOptedOut() { return !mCoreCallbacks.isEnabled(); } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt index 9bba051eb..579bfb450 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt @@ -1,7 +1,6 @@ package com.mparticle.rokt import com.mparticle.MParticle -import com.mparticle.internal.CoreCallbacks import com.mparticle.kits.Rokt /** @@ -21,9 +20,6 @@ object MParticleRokt { } private fun createRokt(mParticle: MParticle): Rokt { - val configManager = mParticle.Internal().configManager val kitManager = mParticle.Internal().kitManager - return Rokt(kitManager) { - (configManager as? CoreCallbacks)?.isEnabled() ?: (mParticle.getOptOut() != true) - } + return Rokt(kitManager) } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt index c7bc4bc78..6661b7a8f 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import java.lang.ref.WeakReference -class Rokt internal constructor(private val mKitManager: KitManager, private val isEnabledProvider: () -> Boolean) { +class Rokt internal constructor(private val mKitManager: KitManager) { @JvmOverloads fun selectPlacements( identifier: String, @@ -96,7 +96,7 @@ class Rokt internal constructor(private val mKitManager: KitManager, private val return kitInstance to roktBridge } - private fun isEnabled(): Boolean = isEnabledProvider() + private fun isEnabled(): Boolean = mKitManager.isEnabled private fun buildPlacementOptions(): PlacementOptions = PlacementOptions( jointSdkSelectPlacements = System.currentTimeMillis(), diff --git a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt index fdc23686f..4ca6d857a 100644 --- a/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt +++ b/kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt @@ -94,7 +94,8 @@ class RoktTest { `when`(identityApi.currentUser).thenReturn(mParticleUser) `when`(kitManager.isKitActive(MParticle.ServiceProviders.ROKT)).thenReturn(true) `when`(kitManager.getKitInstance(MParticle.ServiceProviders.ROKT)).thenReturn(roktKit) - rokt = Rokt(kitManager) { configManager.enabled } + `when`(kitManager.isEnabled).thenAnswer { configManager.enabled } + rokt = Rokt(kitManager) } @Test From 73c65a87a4e049cf7f938dcc4b2a2858717acdbe Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 13:18:25 -0400 Subject: [PATCH 19/23] chore: remove unused imports in KitManagerImpl tests Clean up stale imports in KitManagerImplTest so android-kit-base ktlint test source checks pass in CI. --- .../kotlin/com/mparticle/kits/KitManagerImplTest.kt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt index f40c62aff..7e7091af9 100644 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt +++ b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt @@ -1,7 +1,5 @@ package com.mparticle.kits -import android.content.Context -import android.graphics.Typeface import android.os.Looper import android.os.SystemClock import com.mparticle.BaseEvent @@ -17,7 +15,6 @@ import com.mparticle.consent.GDPRConsent import com.mparticle.identity.IdentityApi import com.mparticle.identity.MParticleUser import com.mparticle.internal.CoreCallbacks -import com.mparticle.internal.Logger import com.mparticle.internal.MPUtility import com.mparticle.internal.SideloadedKit import com.mparticle.kits.KitIntegration.ModifyIdentityListener @@ -29,21 +26,15 @@ import com.mparticle.mock.MockMParticle import com.mparticle.testutils.TestingUtils import junit.framework.TestCase import junit.framework.TestCase.assertEquals -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.test.runTest import org.json.JSONArray import org.json.JSONException import org.json.JSONObject import org.junit.Assert import org.junit.Assert.assertNull -import org.junit.Assert.assertSame import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito @@ -55,7 +46,6 @@ import org.mockito.Mockito.withSettings import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.modules.junit4.PowerMockRunner -import java.lang.ref.WeakReference import java.util.Arrays import java.util.LinkedList import java.util.concurrent.ConcurrentHashMap From df4219839aed75f0d587bd83662dafa234bdb60f Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Wed, 13 May 2026 13:29:36 -0400 Subject: [PATCH 20/23] fix: resolve rokt and buildSrc lint and ci compatibility issues Replace replaceFirstChar in buildSrc for older Kotlin compatibility and address rokt ktlint violations by suppressing Java-style accessor naming and wrapping long warning messages. --- .../com/mparticle/MavenCentralPublish.kt | 11 ++++++++-- .../mparticle/kits/MParticleRoktExtensions.kt | 2 ++ .../mparticle/kits/RoktKitRequestHelper.kt | 21 +++++++++++++------ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/buildSrc/src/main/kotlin/com/mparticle/MavenCentralPublish.kt b/buildSrc/src/main/kotlin/com/mparticle/MavenCentralPublish.kt index e593c6466..840486690 100644 --- a/buildSrc/src/main/kotlin/com/mparticle/MavenCentralPublish.kt +++ b/buildSrc/src/main/kotlin/com/mparticle/MavenCentralPublish.kt @@ -77,14 +77,15 @@ fun Project.configureMavenPublishing(mparticleMavenPublish: MParticleMavenPublis } } + val capitalizedPublicationName = publicationName.capitalizeForTaskName() val validateTaskName = - "validatePomFor${publicationName.replaceFirstChar { it.uppercaseChar() }}Publication" + "validatePomFor${capitalizedPublicationName}Publication" tasks.register(validateTaskName, ValidatePomTask::class.java) { description = "Validates the generated POM file for the '$publicationName' publication." group = "verification" pomFile.set(project.layout.buildDirectory.file("publications/$publicationName/pom-default.xml")) - dependsOn("generatePomFileFor${publicationName.replaceFirstChar { it.uppercaseChar() }}Publication") + dependsOn("generatePomFileFor${capitalizedPublicationName}Publication") } tasks.withType(PublishToMavenLocal::class.java).configureEach { @@ -94,3 +95,9 @@ fun Project.configureMavenPublishing(mparticleMavenPublish: MParticleMavenPublis } } } + +private fun String.capitalizeForTaskName(): String = if (isEmpty()) { + this +} else { + substring(0, 1).uppercase() + substring(1) +} diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt index 579bfb450..817d004ab 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt @@ -12,9 +12,11 @@ fun MParticle.Rokt(): Rokt = createRokt(this) * Java-friendly accessors for the legacy Rokt API object. */ object MParticleRokt { + @Suppress("FunctionName") @JvmStatic fun Rokt(mParticle: MParticle?): Rokt? = mParticle?.let { createRokt(it) } + @Suppress("FunctionName") @JvmStatic fun Rokt(): Rokt? = MParticle.getInstance()?.let { createRokt(it) } } diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt index a808ebe89..ba5778986 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktKitRequestHelper.kt @@ -155,17 +155,26 @@ internal object RoktKitRequestHelper { if (emailMismatch || (hashedEmailMismatch && selectedIdentityType != null)) { if (emailMismatch && existingEmail != null) { + val emailMismatchMessage = + "The existing email on the user ($existingEmail) does not match " + + "the email passed to selectPlacements ($email). " + + "Please make sure to sync the email identity to mParticle " + + "as soon as it's available. " + + "Identifying user with the provided email before continuing " + + "to selectPlacements." Logger.warning( - "The existing email on the user ($existingEmail) does not match the email passed to selectPlacements ($email). " + - "Please make sure to sync the email identity to mParticle as soon as it's available. " + - "Identifying user with the provided email before continuing to selectPlacements.", + emailMismatchMessage, ) } else if (hashedEmailMismatch && existingHashedEmail != null) { - Logger.warning( + val hashedEmailMismatchMessage = "The existing hashed email on the user ($existingHashedEmail) does not match " + "the hashed email passed to selectPlacements ($hashedEmail). " + - "Please make sure to sync the hashed email identity to mParticle as soon as it's available. " + - "Identifying user with the provided hashed email before continuing to selectPlacements.", + "Please make sure to sync the hashed email identity to mParticle " + + "as soon as it's available. " + + "Identifying user with the provided hashed email before continuing " + + "to selectPlacements." + Logger.warning( + hashedEmailMismatchMessage, ) } From f0870f73223fd5a5360e3c1f1e803f7b63293275 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Thu, 14 May 2026 09:25:38 -0400 Subject: [PATCH 21/23] refactor: tighten Rokt accessor API and restore legacy docs Keep only the no-arg public Rokt accessor with explicit start precondition, restore original public method documentation text, and make prepareAttributesAsync internal per review feedback. --- ...icleRoktExtensions.kt => MParticleRokt.kt} | 16 +++--- .../main/kotlin/com/mparticle/kits/Rokt.kt | 51 ++++++++++++++++++- 2 files changed, 56 insertions(+), 11 deletions(-) rename kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/{MParticleRoktExtensions.kt => MParticleRokt.kt} (53%) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt similarity index 53% rename from kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt rename to kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt index 817d004ab..dfdab2384 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRoktExtensions.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt @@ -3,22 +3,18 @@ package com.mparticle.rokt import com.mparticle.MParticle import com.mparticle.kits.Rokt -/** - * Kotlin-friendly accessor for the legacy Rokt API object. - */ -fun MParticle.Rokt(): Rokt = createRokt(this) - /** * Java-friendly accessors for the legacy Rokt API object. */ object MParticleRokt { @Suppress("FunctionName") @JvmStatic - fun Rokt(mParticle: MParticle?): Rokt? = mParticle?.let { createRokt(it) } - - @Suppress("FunctionName") - @JvmStatic - fun Rokt(): Rokt? = MParticle.getInstance()?.let { createRokt(it) } + fun Rokt(): Rokt { + val mParticle = requireNotNull(MParticle.getInstance()) { + "MParticle must be started before calling MParticleRokt.Rokt()" + } + return createRokt(mParticle) + } } private fun createRokt(mParticle: MParticle): Rokt { diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt index 6661b7a8f..0a186ae82 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/Rokt.kt @@ -12,7 +12,20 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import java.lang.ref.WeakReference +/** + * Public facade for interacting with the Rokt Kit through mParticle. + */ class Rokt internal constructor(private val mKitManager: KitManager) { + /** + * Display a Rokt placement with the specified parameters. + * + * @param identifier The placement identifier + * @param attributes User attributes to pass to Rokt + * @param callbacks Optional callback for Rokt events + * @param embeddedViews Optional map of embedded view placeholders + * @param fontTypefaces Optional map of font typefaces + * @param config Optional Rokt configuration + */ @JvmOverloads fun selectPlacements( identifier: String, @@ -43,37 +56,73 @@ class Rokt internal constructor(private val mKitManager: KitManager) { } } + /** + * Get a Flow of Rokt events for the specified identifier. + * + * @param identifier The placement identifier to listen for events + * @return A Flow emitting RoktEvent objects + */ fun events(identifier: String): Flow = if (isEnabled()) { resolveRoktKit()?.second?.events(identifier) ?: flowOf() } else { flowOf() } + /** + * Notify Rokt that a purchase has been finalized. + * + * @param placementId The placement identifier + * @param catalogItemId The catalog item identifier + * @param status Whether the purchase was successful + */ fun purchaseFinalized(placementId: String, catalogItemId: String, status: Boolean) { if (isEnabled()) { resolveRoktKit()?.second?.purchaseFinalized(placementId, catalogItemId, status) } } + /** + * Close any active Rokt placements. + */ fun close() { if (isEnabled()) { resolveRoktKit()?.second?.close() } } + /** + * Set the session id to use for the next execute call. + * + * This is useful for cases where you have a session id from a non-native integration, + * e.g. WebView, and you want the session to be consistent across integrations. + * + * **Note:** Empty strings are ignored and will not update the session. + * + * @param sessionId The session id to be set. Must be a non-empty string. + */ fun setSessionId(sessionId: String) { if (isEnabled()) { resolveRoktKit()?.second?.setSessionId(sessionId) } } + /** + * Get the session id to use within a non-native integration e.g. WebView. + * + * @return The session id or null if no session is present or SDK is not initialized. + */ fun getSessionId(): String? = if (isEnabled()) { resolveRoktKit()?.second?.getSessionId() } else { null } - fun prepareAttributesAsync(attributes: Map) { + /** + * Prepare attributes asynchronously before executing a placement. + * + * @param attributes The attributes to prepare + */ + internal fun prepareAttributesAsync(attributes: Map) { if (isEnabled()) { val resolved = resolveRoktKit() if (resolved != null) { From 33d5dfd99b4e92ffc752d32ef11c9f9f3e9bbc77 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Thu, 14 May 2026 09:42:25 -0400 Subject: [PATCH 22/23] refactor: align MParticleRokt package with kit module path Update MParticleRokt package to com.mparticle.kits so declaration matches the file location and resolves package/path review feedback. --- .../rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt index dfdab2384..685413ede 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt @@ -1,4 +1,4 @@ -package com.mparticle.rokt +package com.mparticle.kits import com.mparticle.MParticle import com.mparticle.kits.Rokt From 4aec17d723db03dfab1d88e4ea4fc675cf917881 Mon Sep 17 00:00:00 2001 From: Denis Chilik Date: Thu, 14 May 2026 09:52:58 -0400 Subject: [PATCH 23/23] refactor: cache MParticleRokt accessor instance Keep a synchronized singleton Rokt instance in MParticleRokt to avoid repeated allocations while preserving the explicit start precondition. --- .../main/kotlin/com/mparticle/kits/MParticleRokt.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt index 685413ede..12a079a93 100644 --- a/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt +++ b/kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt @@ -1,19 +1,28 @@ package com.mparticle.kits import com.mparticle.MParticle -import com.mparticle.kits.Rokt /** * Java-friendly accessors for the legacy Rokt API object. */ object MParticleRokt { + @Volatile + private var rokt: Rokt? = null + @Suppress("FunctionName") @JvmStatic fun Rokt(): Rokt { val mParticle = requireNotNull(MParticle.getInstance()) { "MParticle must be started before calling MParticleRokt.Rokt()" } - return createRokt(mParticle) + + synchronized(this) { + rokt?.let { return it } + + return createRokt(mParticle).also { + rokt = it + } + } } }