Skip to content

Commit 7965fd2

Browse files
committed
Add SMP + Legacy Provider adaptor
Signed-off-by: Max Pinheiro <max.pinheiro@fluxon.com>
1 parent 2f1b956 commit 7965fd2

19 files changed

Lines changed: 1318 additions & 169 deletions

README.md

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,11 @@ coroutineScope.launch(Dispatchers.Default) {
139139
}
140140
```
141141

142+
After `initialize()` returns, `setProviderAndWait` waits for the provider’s `status` to move off `NotReady` and `Reconciling`. That is the provider’s contract to fulfill; the SDK does not time out. If a provider never updates `status` correctly, the call never completes. If you need a maximum wait time, wrap the call in your own `withTimeout` from `kotlinx.coroutines` (or similar) at the application level.
143+
142144
Asynchronous API that doesn't wait is also available. It's useful when you want to set a provider and continue with other tasks.
143145

144-
However, flag evaluations are only possible after the provider is Ready.
146+
However, flag evaluations are only possible after the provider is `OpenFeatureStatus.Ready`. The built-in `NoOpProvider` reports `Ready` after initialization and returns default values for flags (a lightweight placeholder until you register a real provider).
145147

146148
```kotlin
147149
OpenFeatureAPI.setProvider(MyProvider()) // can pass a dispatcher here
@@ -412,11 +414,17 @@ in an Android app.
412414

413415
### Develop a provider
414416

415-
To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency.
416-
You’ll then need to write the provider by implementing the `FeatureProvider` interface exported by the OpenFeature SDK.
417+
Providers are developed in dedicated projects that declare the OpenFeature SDK as a dependency. Each provider must implement the `StateManagingProvider` interface exported by the OpenFeature SDK.
418+
419+
The provider must keep `status` and `observe()` consistent: each time the provider transitions between `OpenFeatureStatus.NotReady`, `OpenFeatureStatus.Reconciling`, and `OpenFeatureStatus.Ready`, it must update `_status` and emit the corresponding `OpenFeatureProviderEvents` (for example, `OpenFeatureStatus.Reconciling` paired with `ProviderReconciling()`). The SDK derives `statusFlow` from `status`, and application-level handlers registered via `OpenFeatureAPI.observe()` receive the emitted events — inconsistency between the two will produce contradictory state to callers.
417420

418421
```kotlin
419-
class NewProvider(override val hooks: List<Hook<*>>, override val metadata: ProviderMetadata) : FeatureProvider {
422+
class NewProvider(override val hooks: List<Hook<*>>, override val metadata: ProviderMetadata) : StateManagingProvider {
423+
private val _status = MutableStateFlow(OpenFeatureStatus.NotReady)
424+
override val status: StateFlow<OpenFeatureStatus> = _status.asStateFlow()
425+
426+
private val events = MutableSharedFlow<OpenFeatureProviderEvents>(replay = 1, extraBufferCapacity = 5)
427+
420428
override fun getBooleanEvaluation(
421429
key: String,
422430
defaultValue: Boolean,
@@ -459,26 +467,43 @@ class NewProvider(override val hooks: List<Hook<*>>, override val metadata: Prov
459467

460468
override suspend fun initialize(initialContext: EvaluationContext?) {
461469
// add context-aware provider initialization
470+
471+
_status.value = OpenFeatureStatus.Ready
472+
events.emit(OpenFeatureProviderEvents.ProviderReady())
462473
}
463474

464475
override suspend fun onContextSet(oldContext: EvaluationContext?, newContext: EvaluationContext) {
476+
_status.value = OpenFeatureStatus.Reconciling
477+
events.emit(OpenFeatureProviderEvents.ProviderReconciling())
478+
465479
// add necessary changes on context change
480+
481+
_status.value = OpenFeatureStatus.Ready
482+
events.emit(OpenFeatureProviderEvents.ProviderReady())
483+
}
484+
485+
override fun shutdown() {
486+
_status.value = OpenFeatureStatus.NotReady
487+
events.tryEmit(OpenFeatureProviderEvents.ProviderNotReady)
488+
// add necessary closure on shutdown
466489
}
467490

468491
override fun track(
469-
trackingEventName: String,
470-
context: EvaluationContext?,
471-
details: TrackingEventDetails?
492+
trackingEventName: String,
493+
context: EvaluationContext?,
494+
details: TrackingEventDetails?
472495
) {
473-
// Optionally track an event
474-
}
475-
476-
override fun observe(): Flow<OpenFeatureProviderEvents> {
477-
// Optionally return a `Flow` of OpenFeatureProviderEvents
496+
// Optionally track an event
478497
}
498+
499+
override fun observe(): Flow<OpenFeatureProviderEvents> = events
479500
}
480501
```
481502

503+
#### `FeatureProvider` DEPRECATION
504+
505+
`FeatureProvider` is still supported although `StateManagingProvider` is preferred for new providers. It should be noted that `FeatureProvider` is a legacy behavior and will be removed in the next major version, due to its possible race condition in the presence of multi-threading.
506+
482507
> Built a new provider? [Let us know](https://github.com/open-feature/openfeature.dev/issues/new?assignees=&labels=provider&projects=&template=document-provider.yaml&title=%5BProvider%5D%3A+) so we can add it to the docs!
483508
484509
### Develop a hook

kotlin-sdk/api/android/kotlin-sdk.api

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public final class dev/openfeature/kotlin/sdk/ImmutableStructure : dev/openfeatu
232232
public fun keySet ()Ljava/util/Set;
233233
}
234234

235-
public class dev/openfeature/kotlin/sdk/NoOpProvider : dev/openfeature/kotlin/sdk/FeatureProvider {
235+
public class dev/openfeature/kotlin/sdk/NoOpProvider : dev/openfeature/kotlin/sdk/StateManagingProvider {
236236
public fun <init> ()V
237237
public fun <init> (Ljava/util/List;)V
238238
public synthetic fun <init> (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -242,6 +242,7 @@ public class dev/openfeature/kotlin/sdk/NoOpProvider : dev/openfeature/kotlin/sd
242242
public fun getIntegerEvaluation (Ljava/lang/String;ILdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
243243
public fun getMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
244244
public fun getObjectEvaluation (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
245+
public fun getStatus ()Lkotlinx/coroutines/flow/StateFlow;
245246
public fun getStringEvaluation (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
246247
public fun initialize (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
247248
public fun observe ()Lkotlinx/coroutines/flow/Flow;
@@ -273,7 +274,7 @@ public final class dev/openfeature/kotlin/sdk/OpenFeatureAPI {
273274
public final fun getHooks ()Ljava/util/List;
274275
public final fun getProvider ()Ldev/openfeature/kotlin/sdk/FeatureProvider;
275276
public final fun getProviderMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
276-
public final fun getProvidersFlow ()Lkotlinx/coroutines/flow/MutableStateFlow;
277+
public final fun getProvidersFlow ()Lkotlinx/coroutines/flow/StateFlow;
277278
public final fun getStatus ()Ldev/openfeature/kotlin/sdk/OpenFeatureStatus;
278279
public final fun getStatusFlow ()Lkotlinx/coroutines/flow/Flow;
279280
public final fun setEvaluationContext (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlinx/coroutines/CoroutineDispatcher;)V
@@ -402,6 +403,18 @@ public final class dev/openfeature/kotlin/sdk/Reason : java/lang/Enum {
402403
public static fun values ()[Ldev/openfeature/kotlin/sdk/Reason;
403404
}
404405

406+
public abstract interface class dev/openfeature/kotlin/sdk/StateManagingProvider : dev/openfeature/kotlin/sdk/FeatureProvider {
407+
public abstract fun getStatus ()Lkotlinx/coroutines/flow/StateFlow;
408+
public abstract fun initialize (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
409+
public abstract fun observe ()Lkotlinx/coroutines/flow/Flow;
410+
public abstract fun onContextSet (Ldev/openfeature/kotlin/sdk/EvaluationContext;Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
411+
public abstract fun shutdown ()V
412+
}
413+
414+
public final class dev/openfeature/kotlin/sdk/StateManagingProvider$DefaultImpls {
415+
public static fun track (Ldev/openfeature/kotlin/sdk/StateManagingProvider;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;Ldev/openfeature/kotlin/sdk/TrackingEventDetails;)V
416+
}
417+
405418
public abstract interface class dev/openfeature/kotlin/sdk/Structure {
406419
public abstract fun asMap ()Ljava/util/Map;
407420
public abstract fun asObjectMap ()Ljava/util/Map;
@@ -709,6 +722,19 @@ public final class dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$P
709722
public fun toString ()Ljava/lang/String;
710723
}
711724

725+
public final class dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderReconciling : dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents {
726+
public fun <init> ()V
727+
public fun <init> (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;)V
728+
public synthetic fun <init> (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
729+
public final fun component1 ()Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;
730+
public final fun copy (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;)Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderReconciling;
731+
public static synthetic fun copy$default (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderReconciling;Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;ILjava/lang/Object;)Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderReconciling;
732+
public fun equals (Ljava/lang/Object;)Z
733+
public fun getEventDetails ()Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;
734+
public fun hashCode ()I
735+
public fun toString ()Ljava/lang/String;
736+
}
737+
712738
public final class dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderStale : dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents {
713739
public fun <init> ()V
714740
public fun <init> (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;)V
@@ -872,7 +898,7 @@ public final class dev/openfeature/kotlin/sdk/multiprovider/FirstSuccessfulStrat
872898
public fun evaluate (Ljava/util/List;Ljava/lang/String;Ljava/lang/Object;Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/jvm/functions/Function4;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
873899
}
874900

875-
public final class dev/openfeature/kotlin/sdk/multiprovider/MultiProvider : dev/openfeature/kotlin/sdk/FeatureProvider {
901+
public final class dev/openfeature/kotlin/sdk/multiprovider/MultiProvider : dev/openfeature/kotlin/sdk/StateManagingProvider {
876902
public static final field Companion Ldev/openfeature/kotlin/sdk/multiprovider/MultiProvider$Companion;
877903
public fun <init> (Ljava/util/List;Ldev/openfeature/kotlin/sdk/multiprovider/MultiProvider$Strategy;)V
878904
public synthetic fun <init> (Ljava/util/List;Ldev/openfeature/kotlin/sdk/multiprovider/MultiProvider$Strategy;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -882,6 +908,7 @@ public final class dev/openfeature/kotlin/sdk/multiprovider/MultiProvider : dev/
882908
public fun getIntegerEvaluation (Ljava/lang/String;ILdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
883909
public fun getMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
884910
public fun getObjectEvaluation (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
911+
public fun getStatus ()Lkotlinx/coroutines/flow/StateFlow;
885912
public final fun getStatusFlow ()Lkotlinx/coroutines/flow/StateFlow;
886913
public fun getStringEvaluation (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
887914
public fun initialize (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

kotlin-sdk/api/jvm/kotlin-sdk.api

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public final class dev/openfeature/kotlin/sdk/ImmutableStructure : dev/openfeatu
232232
public fun keySet ()Ljava/util/Set;
233233
}
234234

235-
public class dev/openfeature/kotlin/sdk/NoOpProvider : dev/openfeature/kotlin/sdk/FeatureProvider {
235+
public class dev/openfeature/kotlin/sdk/NoOpProvider : dev/openfeature/kotlin/sdk/StateManagingProvider {
236236
public fun <init> ()V
237237
public fun <init> (Ljava/util/List;)V
238238
public synthetic fun <init> (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -242,6 +242,7 @@ public class dev/openfeature/kotlin/sdk/NoOpProvider : dev/openfeature/kotlin/sd
242242
public fun getIntegerEvaluation (Ljava/lang/String;ILdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
243243
public fun getMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
244244
public fun getObjectEvaluation (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
245+
public fun getStatus ()Lkotlinx/coroutines/flow/StateFlow;
245246
public fun getStringEvaluation (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
246247
public fun initialize (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
247248
public fun observe ()Lkotlinx/coroutines/flow/Flow;
@@ -273,7 +274,7 @@ public final class dev/openfeature/kotlin/sdk/OpenFeatureAPI {
273274
public final fun getHooks ()Ljava/util/List;
274275
public final fun getProvider ()Ldev/openfeature/kotlin/sdk/FeatureProvider;
275276
public final fun getProviderMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
276-
public final fun getProvidersFlow ()Lkotlinx/coroutines/flow/MutableStateFlow;
277+
public final fun getProvidersFlow ()Lkotlinx/coroutines/flow/StateFlow;
277278
public final fun getStatus ()Ldev/openfeature/kotlin/sdk/OpenFeatureStatus;
278279
public final fun getStatusFlow ()Lkotlinx/coroutines/flow/Flow;
279280
public final fun setEvaluationContext (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlinx/coroutines/CoroutineDispatcher;)V
@@ -402,6 +403,18 @@ public final class dev/openfeature/kotlin/sdk/Reason : java/lang/Enum {
402403
public static fun values ()[Ldev/openfeature/kotlin/sdk/Reason;
403404
}
404405

406+
public abstract interface class dev/openfeature/kotlin/sdk/StateManagingProvider : dev/openfeature/kotlin/sdk/FeatureProvider {
407+
public abstract fun getStatus ()Lkotlinx/coroutines/flow/StateFlow;
408+
public abstract fun initialize (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
409+
public abstract fun observe ()Lkotlinx/coroutines/flow/Flow;
410+
public abstract fun onContextSet (Ldev/openfeature/kotlin/sdk/EvaluationContext;Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
411+
public abstract fun shutdown ()V
412+
}
413+
414+
public final class dev/openfeature/kotlin/sdk/StateManagingProvider$DefaultImpls {
415+
public static fun track (Ldev/openfeature/kotlin/sdk/StateManagingProvider;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;Ldev/openfeature/kotlin/sdk/TrackingEventDetails;)V
416+
}
417+
405418
public abstract interface class dev/openfeature/kotlin/sdk/Structure {
406419
public abstract fun asMap ()Ljava/util/Map;
407420
public abstract fun asObjectMap ()Ljava/util/Map;
@@ -709,6 +722,19 @@ public final class dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$P
709722
public fun toString ()Ljava/lang/String;
710723
}
711724

725+
public final class dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderReconciling : dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents {
726+
public fun <init> ()V
727+
public fun <init> (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;)V
728+
public synthetic fun <init> (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
729+
public final fun component1 ()Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;
730+
public final fun copy (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;)Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderReconciling;
731+
public static synthetic fun copy$default (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderReconciling;Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;ILjava/lang/Object;)Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderReconciling;
732+
public fun equals (Ljava/lang/Object;)Z
733+
public fun getEventDetails ()Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;
734+
public fun hashCode ()I
735+
public fun toString ()Ljava/lang/String;
736+
}
737+
712738
public final class dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$ProviderStale : dev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents {
713739
public fun <init> ()V
714740
public fun <init> (Ldev/openfeature/kotlin/sdk/events/OpenFeatureProviderEvents$EventDetails;)V
@@ -872,7 +898,7 @@ public final class dev/openfeature/kotlin/sdk/multiprovider/FirstSuccessfulStrat
872898
public fun evaluate (Ljava/util/List;Ljava/lang/String;Ljava/lang/Object;Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/jvm/functions/Function4;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
873899
}
874900

875-
public final class dev/openfeature/kotlin/sdk/multiprovider/MultiProvider : dev/openfeature/kotlin/sdk/FeatureProvider {
901+
public final class dev/openfeature/kotlin/sdk/multiprovider/MultiProvider : dev/openfeature/kotlin/sdk/StateManagingProvider {
876902
public static final field Companion Ldev/openfeature/kotlin/sdk/multiprovider/MultiProvider$Companion;
877903
public fun <init> (Ljava/util/List;Ldev/openfeature/kotlin/sdk/multiprovider/MultiProvider$Strategy;)V
878904
public synthetic fun <init> (Ljava/util/List;Ldev/openfeature/kotlin/sdk/multiprovider/MultiProvider$Strategy;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -882,6 +908,7 @@ public final class dev/openfeature/kotlin/sdk/multiprovider/MultiProvider : dev/
882908
public fun getIntegerEvaluation (Ljava/lang/String;ILdev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
883909
public fun getMetadata ()Ldev/openfeature/kotlin/sdk/ProviderMetadata;
884910
public fun getObjectEvaluation (Ljava/lang/String;Ldev/openfeature/kotlin/sdk/Value;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
911+
public fun getStatus ()Lkotlinx/coroutines/flow/StateFlow;
885912
public final fun getStatusFlow ()Lkotlinx/coroutines/flow/StateFlow;
886913
public fun getStringEvaluation (Ljava/lang/String;Ljava/lang/String;Ldev/openfeature/kotlin/sdk/EvaluationContext;)Ldev/openfeature/kotlin/sdk/ProviderEvaluation;
887914
public fun initialize (Ldev/openfeature/kotlin/sdk/EvaluationContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

0 commit comments

Comments
 (0)