diff --git a/sdk/src/main/java/com/amplitude/experiment/DefaultExperimentClient.kt b/sdk/src/main/java/com/amplitude/experiment/DefaultExperimentClient.kt index 4024d3c..d764a27 100644 --- a/sdk/src/main/java/com/amplitude/experiment/DefaultExperimentClient.kt +++ b/sdk/src/main/java/com/amplitude/experiment/DefaultExperimentClient.kt @@ -2,6 +2,7 @@ package com.amplitude.experiment import com.amplitude.experiment.evaluation.EvaluationEngineImpl import com.amplitude.experiment.evaluation.EvaluationFlag +import com.amplitude.experiment.evaluation.json import com.amplitude.experiment.evaluation.topologicalSort import com.amplitude.experiment.storage.LoadStoreCache import com.amplitude.experiment.storage.Storage @@ -21,6 +22,7 @@ import com.amplitude.experiment.util.merge import com.amplitude.experiment.util.toEvaluationContext import com.amplitude.experiment.util.toJson import com.amplitude.experiment.util.toVariant +import kotlinx.serialization.decodeFromString import okhttp3.Call import okhttp3.Callback import okhttp3.HttpUrl @@ -70,6 +72,7 @@ internal class DefaultExperimentClient internal constructor( init { this.variants.load() this.flags.load() + mergeInitialFlagsWithStorage() } private val backoffLock = Any() @@ -362,6 +365,7 @@ internal class DefaultExperimentClient internal constructor( this.flags.clear() this.flags.putAll(flags) this.flags.store() + mergeInitialFlagsWithStorage() } } } @@ -649,6 +653,17 @@ internal class DefaultExperimentClient internal constructor( } return defaultVariantAndSource } + + private fun mergeInitialFlagsWithStorage() { + if (config.initialFlags != null) { + val initialFlags: List = json.decodeFromString(config.initialFlags) + for (flag in initialFlags) { + if (this.flags.get(flag.key) == null) { + this.flags.put(flag.key, flag) + } + } + } + } } data class VariantAndSource( diff --git a/sdk/src/main/java/com/amplitude/experiment/ExperimentClient.kt b/sdk/src/main/java/com/amplitude/experiment/ExperimentClient.kt index abb9eea..8678313 100644 --- a/sdk/src/main/java/com/amplitude/experiment/ExperimentClient.kt +++ b/sdk/src/main/java/com/amplitude/experiment/ExperimentClient.kt @@ -26,7 +26,7 @@ interface ExperimentClient { * @see fetch * @see variant */ - fun start(user: ExperimentUser?): Future + fun start(user: ExperimentUser? = null): Future /** * Stop the local flag configuration poller. diff --git a/sdk/src/main/java/com/amplitude/experiment/ExperimentConfig.kt b/sdk/src/main/java/com/amplitude/experiment/ExperimentConfig.kt index 8a7ee58..089f536 100644 --- a/sdk/src/main/java/com/amplitude/experiment/ExperimentConfig.kt +++ b/sdk/src/main/java/com/amplitude/experiment/ExperimentConfig.kt @@ -26,6 +26,8 @@ class ExperimentConfig internal constructor( @JvmField val fallbackVariant: Variant = Defaults.FALLBACK_VARIANT, @JvmField + val initialFlags: String? = Defaults.INITIAL_FLAGS, + @JvmField val initialVariants: Map = Defaults.INITIAL_VARIANTS, @JvmField val source: Source = Defaults.SOURCE, @@ -81,6 +83,11 @@ class ExperimentConfig internal constructor( */ val FALLBACK_VARIANT: Variant = Variant() + /** + * null + */ + val INITIAL_FLAGS: String? = null + /** * Empty Map */ @@ -165,6 +172,7 @@ class ExperimentConfig internal constructor( private var debug = Defaults.DEBUG private var instanceName = Defaults.INSTANCE_NAME private var fallbackVariant = Defaults.FALLBACK_VARIANT + private var initialFlags = Defaults.INITIAL_FLAGS private var initialVariants = Defaults.INITIAL_VARIANTS private var source = Defaults.SOURCE private var serverUrl = Defaults.SERVER_URL @@ -192,6 +200,10 @@ class ExperimentConfig internal constructor( this.fallbackVariant = fallbackVariant } + fun initialFlags(initialFlags: String?) = apply { + this.initialFlags = initialFlags + } + fun initialVariants(initialVariants: Map) = apply { this.initialVariants = initialVariants } @@ -254,6 +266,7 @@ class ExperimentConfig internal constructor( debug = debug, instanceName = instanceName, fallbackVariant = fallbackVariant, + initialFlags = initialFlags, initialVariants = initialVariants, source = source, serverUrl = serverUrl, @@ -277,6 +290,7 @@ class ExperimentConfig internal constructor( .debug(debug) .instanceName(instanceName) .fallbackVariant(fallbackVariant) + .initialFlags(initialFlags) .initialVariants(initialVariants) .source(source) .serverUrl(serverUrl) diff --git a/sdk/src/test/java/com/amplitude/experiment/ExperimentClientTest.kt b/sdk/src/test/java/com/amplitude/experiment/ExperimentClientTest.kt index 64bd316..2cf718c 100644 --- a/sdk/src/test/java/com/amplitude/experiment/ExperimentClientTest.kt +++ b/sdk/src/test/java/com/amplitude/experiment/ExperimentClientTest.kt @@ -1152,4 +1152,52 @@ class ExperimentClientTest { Assert.assertEquals(array::class, variant.payload!!::class) Assert.assertEquals(array.toString(), variant.payload.toString()) } + + @Test + fun `initial flags`() { + val storage = MockStorage() + // Flag, sdk-ci-test-local is modified to always return off + val initialFlags = """ + [ + {"key":"sdk-ci-test-local","metadata":{"deployed":true,"evaluationMode":"local","flagType":"release","flagVersion":1},"segments":[{"metadata":{"segmentName":"All Other Users"},"variant":"off"}],"variants":{"off":{"key":"off","metadata":{"default":true}},"on":{"key":"on","value":"on"}}}, + {"key":"sdk-ci-test-local-2","metadata":{"deployed":true,"evaluationMode":"local","flagType":"release","flagVersion":1},"segments":[{"metadata":{"segmentName":"All Other Users"},"variant":"on"}],"variants":{"off":{"key":"off","metadata":{"default":true}},"on":{"key":"on","value":"on"}}} + ] + """.trimIndent() + val client = DefaultExperimentClient( + API_KEY, + ExperimentConfig( + initialFlags = initialFlags, + ), + OkHttpClient(), + storage, + Experiment.executorService, + ) + val user = ExperimentUser(userId = "user_id", deviceId = "device_id") + client.setUser(user) + var variant = client.variant("sdk-ci-test-local") + Assert.assertEquals("off", variant.key) + var variant2 = client.variant("sdk-ci-test-local-2") + Assert.assertEquals("on", variant2.key) + // Call start to update the flag, overwrites the initial flag to return on + client.start(user).get() + variant = client.variant("sdk-ci-test-local") + Assert.assertEquals("on", variant.key) + variant2 = client.variant("sdk-ci-test-local-2") + Assert.assertEquals("on", variant2.key) + // Initialize a second client with the same storage to simulate an app restart + val client2 = DefaultExperimentClient( + API_KEY, + ExperimentConfig( + initialFlags = initialFlags, + ), + OkHttpClient(), + storage, + Experiment.executorService, + ) + // Storage flag should take precedent over initial flag + variant = client.variant("sdk-ci-test-local") + Assert.assertEquals("on", variant.key) + variant2 = client.variant("sdk-ci-test-local-2") + Assert.assertEquals("on", variant2.key) + } }