Skip to content

Commit 224b9f5

Browse files
authored
feat(rokt): add kit API parity access (#711)
Add Kotlin MParticle.rokt access so partners can call Rokt kit APIs from the active mParticle instance again. Forward native Rokt SDK Compose events through the kit RoktLayout wrapper and document the Kotlin and Java entry points.
1 parent bf998f3 commit 224b9f5

5 files changed

Lines changed: 137 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
<!-- markdownlint-disable MD024 MD041 -->
2+
13
## [Unreleased]
24

35
### Changed
46

57
- Add support for qualified alpha, beta, and release candidate versions in release workflows.
8+
- Add Kotlin `MParticle.rokt` access and `RoktLayout` event callbacks for the Rokt kit.
69

710
### Removed
811

kits/rokt/rokt/README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,47 @@ This repository contains the [Rokt](https://docs.rokt.com/) integration for the
88

99
```groovy
1010
dependencies {
11-
implementation 'com.mparticle:android-rokt-kit:5+'
11+
implementation 'com.mparticle:android-rokt-kit:6+'
1212
}
1313
```
1414
1515
2. Follow the mParticle Android SDK [quick-start](https://github.com/mParticle/mparticle-android-sdk), then rebuild and launch your app, and verify that you see `"Rokt detected"` in the output of `adb logcat`.
1616
3. Reference mParticle's integration docs below to enable the integration.
1717
18+
## Usage
19+
20+
Kotlin consumers can access the Rokt Kit facade from the mParticle instance:
21+
22+
```kotlin
23+
import com.mparticle.MParticle
24+
import com.mparticle.kits.rokt
25+
26+
MParticle.getInstance()?.rokt?.selectPlacements(
27+
identifier = "RoktExperience",
28+
attributes = attributes,
29+
)
30+
```
31+
32+
Java consumers can use the kit helper:
33+
34+
```java
35+
MParticleRokt.Rokt().selectPlacements("RoktExperience", attributes);
36+
```
37+
38+
Compose integrations can receive native Rokt SDK events from `RoktLayout`:
39+
40+
```kotlin
41+
RoktLayout(
42+
sdkTriggered = true,
43+
identifier = "RoktExperience",
44+
attributes = attributes,
45+
location = "RoktEmbedded1",
46+
onEvent = { event ->
47+
// Handle RoktEvent
48+
},
49+
)
50+
```
51+
1852
## Documentation
1953

2054
[Rokt integration](https://docs.rokt.com/developers/integration-guides/android/overview)

kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/MParticleRokt.kt

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,42 @@ object MParticleRokt {
99
@Volatile
1010
private var rokt: Rokt? = null
1111

12+
@Volatile
13+
private var roktInstance: MParticle? = null
14+
1215
@Suppress("FunctionName")
1316
@JvmStatic
1417
fun Rokt(): Rokt {
1518
val mParticle = requireNotNull(MParticle.getInstance()) {
1619
"MParticle must be started before calling MParticleRokt.Rokt()"
1720
}
1821

19-
synchronized(this) {
20-
rokt?.let { return it }
22+
return roktFor(mParticle)
23+
}
24+
25+
internal fun roktFor(mParticle: MParticle): Rokt = synchronized(this) {
26+
val existing = rokt
27+
if (existing != null && roktInstance === mParticle) {
28+
return existing
29+
}
2130

22-
return createRokt(mParticle).also {
23-
rokt = it
24-
}
31+
return createRokt(mParticle).also {
32+
rokt = it
33+
roktInstance = mParticle
2534
}
2635
}
2736
}
2837

38+
/**
39+
* Returns the Rokt Kit facade bound to this mParticle instance.
40+
*
41+
* Kotlin consumers can use this property to call Rokt Kit APIs through
42+
* `MParticle.getInstance()?.rokt`. Java consumers should continue to use
43+
* [MParticleRokt.Rokt].
44+
*/
45+
val MParticle.rokt: Rokt
46+
get() = MParticleRokt.roktFor(this)
47+
2948
private fun createRokt(mParticle: MParticle): Rokt {
3049
val kitManager = mParticle.Internal().kitManager
3150
return Rokt(kitManager)

kits/rokt/rokt/src/main/kotlin/com/mparticle/kits/RoktLayout.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,19 @@ import androidx.compose.runtime.remember
77
import androidx.compose.ui.Modifier
88
import com.rokt.roktsdk.PlacementOptions
99
import com.rokt.roktsdk.RoktConfig
10+
import com.rokt.roktsdk.RoktEvent
1011

12+
/**
13+
* Rokt Jetpack Compose placement wrapper with mParticle attribute enrichment.
14+
*
15+
* @param sdkTriggered Whether the Rokt SDK should trigger placement selection.
16+
* @param identifier The placement identifier.
17+
* @param attributes Attributes to enrich through mParticle before rendering.
18+
* @param location The Rokt placement location.
19+
* @param modifier Optional Compose modifier for the placement.
20+
* @param config Optional Rokt SDK configuration.
21+
* @param onEvent Callback for native Rokt SDK placement events.
22+
*/
1123
@Composable
1224
@Suppress("FunctionName")
1325
fun RoktLayout(
@@ -17,6 +29,7 @@ fun RoktLayout(
1729
location: String,
1830
modifier: Modifier = Modifier,
1931
config: RoktConfig? = null,
32+
onEvent: (RoktEvent) -> Unit = {},
2033
) {
2134
var placementOptions: PlacementOptions? = null
2235
val instance = RoktKit.instance
@@ -43,6 +56,7 @@ fun RoktLayout(
4356
location = location,
4457
config = config,
4558
placementOptions = placementOptions,
59+
onEvent = onEvent,
4660
)
4761
}
4862
}

kits/rokt/rokt/src/test/kotlin/com/mparticle/kits/RoktTest.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ class RoktTest {
5454
private lateinit var configManager: FakeConfigManager
5555
private lateinit var rokt: Rokt
5656

57+
private data class RoktFacadeFixture(
58+
val mParticle: MParticle,
59+
val roktListener: RoktKitBridge,
60+
)
61+
5762
class FakeConfigManager(var enabled: Boolean = true) {
5863
fun isEnabled(): Boolean = enabled
5964
}
@@ -279,4 +284,60 @@ class RoktTest {
279284
)
280285
assertTrue(optionsCaptor.value.jointSdkSelectPlacements >= currentTimeMillis)
281286
}
287+
288+
@Test
289+
fun testMParticleRoktExtensionEvents_delegatesToCurrentInstance() {
290+
val fixture = createMParticleWithRoktKit()
291+
MParticle.setInstance(fixture.mParticle)
292+
val expectedFlow: Flow<RoktEvent> = flowOf()
293+
`when`(fixture.roktListener.events("identifier")).thenReturn(expectedFlow)
294+
295+
val result = MParticle.getInstance()!!.rokt.events("identifier")
296+
297+
verify(fixture.roktListener).events("identifier")
298+
assertEquals(expectedFlow, result)
299+
}
300+
301+
@Test
302+
fun testMParticleRoktExtensionUsesNewFacadeAfterInstanceChanges() {
303+
val firstFixture = createMParticleWithRoktKit()
304+
val secondFixture = createMParticleWithRoktKit()
305+
val firstFlow: Flow<RoktEvent> = flowOf()
306+
val secondFlow: Flow<RoktEvent> = flowOf()
307+
`when`(firstFixture.roktListener.events("first")).thenReturn(firstFlow)
308+
`when`(secondFixture.roktListener.events("second")).thenReturn(secondFlow)
309+
310+
MParticle.setInstance(firstFixture.mParticle)
311+
val firstResult = MParticle.getInstance()!!.rokt.events("first")
312+
313+
MParticle.setInstance(secondFixture.mParticle)
314+
val secondResult = MParticle.getInstance()!!.rokt.events("second")
315+
316+
assertEquals(firstFlow, firstResult)
317+
assertEquals(secondFlow, secondResult)
318+
verify(firstFixture.roktListener).events("first")
319+
verify(secondFixture.roktListener).events("second")
320+
verify(firstFixture.roktListener, never()).events("second")
321+
}
322+
323+
private fun createMParticleWithRoktKit(): RoktFacadeFixture {
324+
val mParticle = org.mockito.Mockito.mock(MParticle::class.java)
325+
val internal = org.mockito.Mockito.mock(MParticle.Internal::class.java)
326+
val kitManager =
327+
org.mockito.Mockito.mock(MParticle.Internal::class.java.getMethod("getKitManager").returnType) as KitManager
328+
val roktKit =
329+
org.mockito.Mockito.mock(
330+
KitIntegration::class.java,
331+
withSettings().extraInterfaces(RoktKitBridge::class.java),
332+
)
333+
val roktListener = roktKit as RoktKitBridge
334+
335+
`when`(mParticle.Internal()).thenReturn(internal)
336+
org.mockito.Mockito.doReturn(kitManager).`when`(internal).kitManager
337+
`when`(kitManager.isEnabled).thenReturn(true)
338+
`when`(kitManager.isKitActive(MParticle.ServiceProviders.ROKT)).thenReturn(true)
339+
`when`(kitManager.getKitInstance(MParticle.ServiceProviders.ROKT)).thenReturn(roktKit)
340+
341+
return RoktFacadeFixture(mParticle, roktListener)
342+
}
282343
}

0 commit comments

Comments
 (0)