Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android/JVM - expose constantController #489

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Any>, namespace: String) {
node.getInvokable<Unit>("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<Any?>("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<Unit>("setTemporaryValues")?.invoke(data, namespace)
}

/**
* Clears any temporary values that were previously set
*/
public fun clearTemporaryValues() {
node.getInvokable<Unit>("clearTemporaryValues")?.invoke()
}

internal object Serializer : NodeWrapperSerializer<ConstantsController>(::ConstantsController)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}