diff --git a/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt index 76ab00b2c..dfae5a8b0 100644 --- a/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt +++ b/android/player/src/main/java/com/intuit/playerui/android/AndroidPlayer.kt @@ -18,6 +18,7 @@ import com.intuit.playerui.core.bridge.Completable import com.intuit.playerui.core.bridge.format import com.intuit.playerui.core.bridge.runtime.PlayerRuntimeConfig import com.intuit.playerui.core.bridge.serialization.format.registerContextualSerializer +import com.intuit.playerui.core.constants.ConstantsController import com.intuit.playerui.core.logger.TapableLogger import com.intuit.playerui.core.player.HeadlessPlayer import com.intuit.playerui.core.player.Player @@ -89,6 +90,8 @@ public class AndroidPlayer private constructor( override val logger: TapableLogger by player::logger + override val constantsController: ConstantsController by player::constantsController + public class Hooks internal constructor(hooks: Player.Hooks) : Player.Hooks by hooks { public class ContextHook : SyncWaterfallHook<(HookContext, Context) -> Context, Context>() { public fun call(context: Context): Context = super.call( diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt new file mode 100644 index 000000000..9a22a4417 --- /dev/null +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/constants/ConstantsController.kt @@ -0,0 +1,46 @@ +package com.intuit.playerui.core.constants +import com.intuit.playerui.core.bridge.Node +import com.intuit.playerui.core.bridge.NodeWrapper +import com.intuit.playerui.core.bridge.getInvokable +import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import kotlinx.serialization.Serializable + +@Serializable(with = ConstantsController.Serializer::class) +public class ConstantsController(override val node: Node) : NodeWrapper { + /** + * Function to add constants to the providers store + * @param data values to add to the constants store + * @param namespace namespace to add the constants under + */ + public fun addConstants(data: Map, namespace: String) { + node.getInvokable("addConstants")?.invoke(data, namespace) + } + + /** + * Function to retrieve constants from the providers store + * @param key Key used for the store access + * @param namespace namespace values were loaded under (defined in the plugin) + * @param fallback Optional - if key doesn't exist in namespace what to return (will return unknown if not provided) + */ + public fun getConstants(key: String, namespace: String, fallback: Any? = null): Any? { + return node.getInvokable("getConstants")?.invoke(key, namespace, fallback) + } + + /** + * Function to set values to temporarily override certain keys in the permanent store + * @param data values to override store with + * @param namespace namespace to override + */ + public fun setTemporaryValues(data: Any, namespace: String) { + node.getInvokable("setTemporaryValues")?.invoke(data, namespace) + } + + /** + * Clears any temporary values that were previously set + */ + public fun clearTemporaryValues() { + node.getInvokable("clearTemporaryValues")?.invoke() + } + + internal object Serializer : NodeWrapperSerializer(::ConstantsController) +} diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt index 04d239891..e49ceaed2 100644 --- a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/HeadlessPlayer.kt @@ -11,6 +11,7 @@ import com.intuit.playerui.core.bridge.runtime.ScriptContext import com.intuit.playerui.core.bridge.runtime.add import com.intuit.playerui.core.bridge.runtime.runtimeFactory import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField +import com.intuit.playerui.core.constants.ConstantsController import com.intuit.playerui.core.experimental.ExperimentalPlayerApi import com.intuit.playerui.core.logger.TapableLogger import com.intuit.playerui.core.player.HeadlessPlayer.Companion.bundledSource @@ -77,6 +78,8 @@ public constructor( override val hooks: Hooks by NodeSerializableField(Hooks.serializer(), NodeSerializableField.CacheStrategy.Full) + override val constantsController: ConstantsController by NodeSerializableField(ConstantsController.serializer(), NodeSerializableField.CacheStrategy.Full) + override val state: PlayerFlowState get() = if (player.isReleased()) { ReleasedState } else { diff --git a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt index 3cb518777..e13bfb750 100644 --- a/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt +++ b/jvm/core/src/main/kotlin/com/intuit/playerui/core/player/Player.kt @@ -6,6 +6,7 @@ import com.intuit.playerui.core.bridge.NodeWrapper import com.intuit.playerui.core.bridge.hooks.NodeSyncHook1 import com.intuit.playerui.core.bridge.serialization.serializers.NodeSerializableField import com.intuit.playerui.core.bridge.serialization.serializers.NodeWrapperSerializer +import com.intuit.playerui.core.constants.ConstantsController import com.intuit.playerui.core.data.DataController import com.intuit.playerui.core.experimental.ExperimentalPlayerApi import com.intuit.playerui.core.expressions.ExpressionController @@ -33,6 +34,8 @@ public abstract class Player : Pluggable { public abstract val logger: TapableLogger + public abstract val constantsController: ConstantsController + /** * Expose [PlayerHooks] which allow consumers to plug * into the flow and subscribe to different events. diff --git a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt index 74d043093..7bf1f925f 100644 --- a/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt +++ b/jvm/core/src/test/kotlin/com/intuit/playerui/core/player/HeadlessPlayerTest.kt @@ -455,4 +455,92 @@ internal class HeadlessPlayerTest : PlayerTest(), ThreadUtils { assertTrue(player.state is ErrorState) } + + @TestTemplate + fun `test constantsController get and set`() = runBlockingTest { + val constantsController = player.constantsController + + val data = mapOf( + "firstname" to "john", + "lastname" to "doe", + "favorite" to mapOf("color" to "red"), + "age" to 1, + ) + + constantsController.addConstants(data = data, namespace = "constants") + + val firstname = constantsController.getConstants(key = "firstname", namespace = "constants") + assertEquals("john", firstname) + + val middleName = constantsController.getConstants(key = "middlename", namespace = "constants") + assertNull(middleName) + + val middleNameSafe = constantsController.getConstants(key = "middlename", namespace = "constants", fallback = "A") + assertEquals("A", middleNameSafe) + + val favoriteColor = constantsController.getConstants(key = "favorite.color", namespace = "constants") + assertEquals("red", favoriteColor) + + val age = constantsController.getConstants(key = "age", namespace = "constants") + assertEquals(1, age) + + val nonExistentNamespace = constantsController.getConstants(key = "test", namespace = "foo") + assertNull(nonExistentNamespace) + + val nonExistentNamespaceWithFallback = constantsController.getConstants(key = "test", namespace = "foo", fallback = "B") + assertEquals("B", nonExistentNamespaceWithFallback) + + // Test and make sure keys override properly + val newData = mapOf( + "favorite" to mapOf("color" to "blue"), + ) + + constantsController.addConstants(data = newData, namespace = "constants") + + val newFavoriteColor = constantsController.getConstants(key = "favorite.color", namespace = "constants") + assertEquals("blue", newFavoriteColor) + } + + @TestTemplate + fun `test constantsController temp override functionality`() = runBlockingTest { + val constantsController = player.constantsController + + // Add initial constants + val data = mapOf( + "firstname" to "john", + "lastname" to "doe", + "favorite" to mapOf("color" to "red"), + ) + constantsController.addConstants(data = data, namespace = "constants") + + // Override with temporary values + val tempData = mapOf( + "firstname" to "jane", + "favorite" to mapOf("color" to "blue"), + ) + constantsController.setTemporaryValues(data = tempData, namespace = "constants") + + // Test temporary override + val firstnameTemp = constantsController.getConstants(key = "firstname", namespace = "constants") + assertEquals("jane", firstnameTemp) + + val favoriteColorTemp = constantsController.getConstants(key = "favorite.color", namespace = "constants") + assertEquals("blue", favoriteColorTemp) + + // Test fallback to original values when temporary values are not present + val lastnameTemp = constantsController.getConstants(key = "lastname", namespace = "constants") + assertEquals("doe", lastnameTemp) + + // Reset temp and values should be the same as the original data + constantsController.clearTemporaryValues() + + val firstname = constantsController.getConstants(key = "firstname", namespace = "constants") + assertEquals("john", firstname) + + val favoriteColor = constantsController.getConstants(key = "favorite.color", namespace = "constants") + assertEquals("red", favoriteColor) + + val lastname = constantsController.getConstants(key = "lastname", namespace = "constants") + assertEquals("doe", lastname) + } }