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
62 changes: 18 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<!-- x-hide-in-docs-end -->
<!-- The 'github-badges' class is used in the docs -->
<p align="center" class="github-badges">
<a href="https://github.com/open-feature/spec/releases/tag/v0.6.0">
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.6.0&color=yellow&style=for-the-badge" />
<a href="https://github.com/open-feature/spec/releases/tag/v0.8.0">
<img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.8.0&color=yellow&style=for-the-badge" />
</a>
<!-- x-release-please-start-version -->
<a href="https://github.com/open-feature/kotlin-sdk/releases/tag/v0.3.3">
Expand Down Expand Up @@ -75,7 +75,7 @@ coroutineScope.launch(Dispatchers.IO) {
| ✅ | [Tracking](#tracking) | Associate user actions with feature flag evaluations. |
| ❌ | [Logging](#logging) | Integrate with popular logging packages. |
| ❌ | [Named clients](#named-clients) | Utilize multiple providers in a single application. |
| | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
| | [Eventing](#eventing) | React to state changes in the provider or flag management system. |
| ✅ | [Shutdown](#shutdown) | Gracefully clean up a provider during application shutdown. |
| ⚠️ | [Extending](#extending) | Extend OpenFeature with custom providers and hooks. |

Expand All @@ -90,10 +90,21 @@ If the provider you're looking for hasn't been created yet, see the [develop a p
Once you've added a provider as a dependency, it can be registered with OpenFeature like this:

```kotlin
OpenFeatureAPI.setProviderAndWait(MyProvider())
coroutineScope.launch(Dispatchers.IO) {
OpenFeatureAPI.setProviderAndWait(MyProvider())
}
```

> Asynchronous API that doesn't wait is also available
Asynchronous API that doesn't wait is also available. It's useful when you want to set a provider and continue with other tasks.

However, flag evaluations are only possible after the provider is Ready.

```kotlin
OpenFeatureAPI.setProvider(MyProvider()) // can pass a dispatcher here
// The provider initialization happens on a coroutine launched on the IO dispatcher.
val status = OpenFeatureAPI.getStatus()
// When status is Ready, flag evaluations can be made
```


### Targeting
Expand Down Expand Up @@ -164,35 +175,6 @@ Logging customization is not yet available in the Kotlin SDK.

Support for named clients is not yet available in the Kotlin SDK.

### Eventing

Events allow you to react to state changes in the provider or underlying flag management system, such as flag definition changes, provider readiness, or error conditions.
Initialization events (`PROVIDER_READY` on success, `PROVIDER_ERROR` on failure) are dispatched for every provider.
Some providers support additional events, such as `PROVIDER_CONFIGURATION_CHANGED`.

Please refer to the documentation of the provider you're using to see what events are supported.

Example usage:
```kotlin
viewModelScope.launch {
OpenFeatureAPI.observe<OpenFeatureEvents.ProviderReady>().collect {
println(">> ProviderReady event received")
}
}

viewModelScope.launch {
OpenFeatureAPI.setProviderAndWait(
ConfidenceFeatureProvider.create(
applicationContext,
clientSecret
),
Dispatchers.IO,
myEvaluationContext
)
}
```

<!-- (It's only possible to observe events from the global `OpenFeatureAPI`, until multiple providers are supported) -->

### Shutdown

Expand Down Expand Up @@ -252,21 +234,13 @@ class NewProvider(override val hooks: List<Hook<*>>, override val metadata: Meta
// resolve a string flag value
}

override fun initialize(initialContext: EvaluationContext?) {
override suspend fun initialize(initialContext: EvaluationContext?) {
// add context-aware provider initialization
}

override fun onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext) {
override suspend fun onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext) {
// add necessary changes on context change
}

override fun observe(): Flow<OpenFeatureEvents> {
// return a `Flow` of the Events
}

override fun getProviderStatus(): OpenFeatureEvents {
// return the event representative of the current Provider Status
}
}
```

Expand Down
33 changes: 23 additions & 10 deletions android/src/main/java/dev/openfeature/sdk/FeatureProvider.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.EventObserver
import dev.openfeature.sdk.events.ProviderStatus
import dev.openfeature.sdk.exceptions.OpenFeatureError
import kotlin.jvm.Throws

interface FeatureProvider : EventObserver, ProviderStatus {
interface FeatureProvider {
val hooks: List<Hook<*>>
val metadata: ProviderMetadata

// Called by OpenFeatureAPI whenever the new Provider is registered
// This function should never throw
fun initialize(initialContext: EvaluationContext?)
/**
* Called by OpenFeatureAPI whenever the new Provider is registered
* This function should block until ready and throw exceptions if it fails to initialize
* @param initialContext any initial context to be set before the provider is ready
*/
@Throws(OpenFeatureError::class)
suspend fun initialize(initialContext: EvaluationContext?)

// Called when the lifecycle of the OpenFeatureClient is over
// to release resources/threads
/**
* Called when the lifecycle of the OpenFeatureClient is over to release resources/threads
*/
fun shutdown()

// Called by OpenFeatureAPI whenever a new EvaluationContext is set by the application
fun onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext)
/**
* Called by OpenFeatureAPI whenever a new EvaluationContext is set by the application
* Perform blocking work here until the provider is ready again or throws an exception
* @param oldContext The old EvaluationContext
* @param newContext The new EvaluationContext
* @throws OpenFeatureError if the provider cannot perform the task
*/
@Throws(OpenFeatureError::class)
suspend fun onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext)

fun getBooleanEvaluation(
key: String,
defaultValue: Boolean,
Expand Down
12 changes: 2 additions & 10 deletions android/src/main/java/dev/openfeature/sdk/NoOpProvider.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package dev.openfeature.sdk

import dev.openfeature.sdk.events.OpenFeatureEvents
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

open class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureProvider {
override val metadata: ProviderMetadata = NoOpProviderMetadata("No-op provider")
override fun initialize(initialContext: EvaluationContext?) {
override suspend fun initialize(initialContext: EvaluationContext?) {
// no-op
}

override fun shutdown() {
// no-op
}

override fun onContextSet(
override suspend fun onContextSet(
oldContext: EvaluationContext?,
newContext: EvaluationContext
) {
Expand Down Expand Up @@ -61,9 +57,5 @@ open class NoOpProvider(override val hooks: List<Hook<*>> = listOf()) : FeatureP
return ProviderEvaluation(defaultValue, "Passed in default", Reason.DEFAULT.toString())
}

override fun observe(): Flow<OpenFeatureEvents> = flowOf()

override fun getProviderStatus(): OpenFeatureEvents = OpenFeatureEvents.ProviderReady

data class NoOpProviderMetadata(override val name: String?) : ProviderMetadata
}
Loading