Skip to content
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
26 changes: 26 additions & 0 deletions android/src/main/java/dev/openfeature/sdk/OpenFeatureAPI.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.awaitReadyOrError
import dev.openfeature.sdk.events.observe
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.flatMapLatest

@Suppress("TooManyFunctions")
object OpenFeatureAPI {
Expand All @@ -24,6 +31,15 @@ object OpenFeatureAPI {
}
}

suspend fun setProviderAndWait(
provider: FeatureProvider,
dispatcher: CoroutineDispatcher,
initialContext: EvaluationContext? = null
) {
setProvider(provider, initialContext)
provider.awaitReadyOrError(dispatcher)
}

fun getProvider(): FeatureProvider? {
return provider
}
Expand Down Expand Up @@ -61,4 +77,14 @@ object OpenFeatureAPI {
fun shutdown() {
provider?.shutdown()
}

/*
Observe events from currently configured Provider.
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal inline fun <reified T : OpenFeatureEvents> observe(): Flow<T> {
return sharedProvidersFlow.flatMapLatest { provider ->
provider.observe<T>()
}
}
}
46 changes: 0 additions & 46 deletions android/src/main/java/dev/openfeature/sdk/async/AsyncClient.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,59 +1,22 @@
package dev.openfeature.sdk.async
package dev.openfeature.sdk.events

import dev.openfeature.sdk.EvaluationContext
import dev.openfeature.sdk.FeatureProvider
import dev.openfeature.sdk.OpenFeatureAPI
import dev.openfeature.sdk.OpenFeatureClient
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.observe
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine

fun OpenFeatureClient.toAsync(): AsyncClient? {
val provider = OpenFeatureAPI.getProvider()
return provider?.let {
AsyncClientImpl(
this,
it
)
}
}

suspend fun OpenFeatureAPI.setProviderAndWait(
provider: FeatureProvider,
dispatcher: CoroutineDispatcher,
initialContext: EvaluationContext? = null
) {
setProvider(provider, initialContext)
provider.awaitReadyOrError(dispatcher)
}

internal fun FeatureProvider.observeProviderReady() = observe<OpenFeatureEvents.ProviderReady>()
.onStart {
if (getProviderStatus() == OpenFeatureEvents.ProviderReady) {
this.emit(OpenFeatureEvents.ProviderReady)
}
}

/*
Observe events from currently configured Provider.
*/
@OptIn(ExperimentalCoroutinesApi::class)
internal inline fun <reified T : OpenFeatureEvents> OpenFeatureAPI.observe(): Flow<T> {
return sharedProvidersFlow.flatMapLatest { provider ->
provider.observe<T>()
}
}

internal fun FeatureProvider.observeProviderError() = observe<OpenFeatureEvents.ProviderError>()
.onStart {
val status = getProviderStatus()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.async.observe
import dev.openfeature.sdk.async.setProviderAndWait
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.exceptions.ErrorCode
import dev.openfeature.sdk.helpers.AlwaysBrokenProvider
Expand Down
81 changes: 1 addition & 80 deletions android/src/test/java/dev/openfeature/sdk/EventsHandlerTest.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.async.observeProviderReady
import dev.openfeature.sdk.async.toAsync
import dev.openfeature.sdk.events.EventHandler
import dev.openfeature.sdk.events.OpenFeatureEvents
import dev.openfeature.sdk.events.observe
import dev.openfeature.sdk.events.observeProviderReady
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
Expand All @@ -15,9 +14,6 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Test
import org.mockito.Mockito.`when`
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import kotlin.time.Duration.Companion.milliseconds

@OptIn(ExperimentalCoroutinesApi::class)
Expand Down Expand Up @@ -179,81 +175,6 @@ class EventsHandlerTest {
Assert.assertTrue(isProviderStale)
}

@Test
fun observe_string_value_from_client_works() = runTest {
val dispatcher = UnconfinedTestDispatcher(testScheduler)
val eventHandler = EventHandler(dispatcher)
val provider = TestFeatureProvider(dispatcher, eventHandler)

provider.emitReady()
val key = "mykey"
val default = "default"
val resultTexts = mutableListOf<String>()

OpenFeatureAPI.setProvider(
mock {
on { getProviderStatus() } doReturn provider.getProviderStatus()
on { observeProviderReady() } doReturn provider.observeProviderReady()
}
)

val mockOpenFeatureClient = mock<OpenFeatureClient> {
on { getStringValue(key, default) } doReturn "text1"
}

// observing the provider status after the provider ready event is published
val job = backgroundScope.launch(dispatcher) {
mockOpenFeatureClient.toAsync()!!
.observeStringValue(key, default)
.take(2)
.collect {
resultTexts.add(it)
}
}

`when`(mockOpenFeatureClient.getStringValue(key, default))
.thenReturn("text2")

provider.emitReady()
job.join()
Assert.assertEquals(listOf("text1", "text2"), resultTexts)
}

@Test
fun observe_string_value_from_client_waits_until_provider_ready() = runTest {
val dispatcher = UnconfinedTestDispatcher(testScheduler)
val eventHandler = EventHandler(dispatcher)
val provider = TestFeatureProvider(dispatcher, eventHandler)
val key = "mykey"
val default = "default"
val resultTexts = mutableListOf<String>()

val mockOpenFeatureClient = mock<OpenFeatureClient> {
on { getStringValue(key, default) } doReturn "text1"
}

OpenFeatureAPI.setProvider(
mock {
on { getProviderStatus() } doReturn provider.getProviderStatus()
on { observeProviderReady() } doReturn provider.observeProviderReady()
}
)

// observing the provider status after the provider ready event is published
val job = backgroundScope.launch(dispatcher) {
mockOpenFeatureClient.toAsync()!!
.observeStringValue(key, default)
.take(1)
.collect {
resultTexts.add(it)
}
}

provider.emitReady()
job.join()
Assert.assertEquals(listOf("text1"), resultTexts)
}

@Test
fun accessing_status_from_provider_works() = runTest {
val dispatcher = UnconfinedTestDispatcher(testScheduler)
Expand Down
2 changes: 0 additions & 2 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import org.gradle.api.initialization.resolve.RepositoriesMode

pluginManagement {
repositories {
google()
Expand Down