feat: State change notification for flags client#3025
Conversation
|
🎯 Code Coverage 🔗 Commit SHA: c8b1143 | Docs | Datadog PR Page | Was this helpful? Give us feedback! |
…ral core subscription class
8cfaa65 to
be53823
Compare
typotter
left a comment
There was a problem hiding this comment.
Thanks Nikita. ptal
| testedManager.updateState(FlagsClientState.Ready) | ||
|
|
||
| // Then - no further notifications after removal | ||
| org.mockito.kotlin.verifyNoMoreInteractions(mockListener) |
| @Test | ||
| fun `M delegate to state manager W state_removeListener()`() { | ||
| // Given | ||
| val mockListener = mock(FlagsStateListener::class.java) |
| @Test | ||
| fun `M delegate to state manager W state_addListener()`() { | ||
| // Given | ||
| val mockListener = mock(FlagsStateListener::class.java) |
| override fun addListener(listener: FlagsStateListener) { /* no-op */ } | ||
| override fun removeListener(listener: FlagsStateListener) { /* no-op */ } |
| /** | ||
| * Observable interface for tracking client state changes. | ||
| * | ||
| * Provides three ways to observe state: | ||
| * - Synchronous: [StateObservable.getCurrentState] for immediate queries (Java-friendly) | ||
| * - Reactive: [StateObservable.flow] for coroutine-based updates (Kotlin) | ||
| * - Callback: [StateObservable.addListener] for traditional observers (Java-friendly) | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * // Synchronous | ||
| * val current = client.state.getCurrentState() | ||
| * | ||
| * // Reactive Flow | ||
| * client.state.flow.collect { state -> /* ... */ } |
| fun `set up`() { | ||
| whenever(mockSdkCore.internalLogger) doReturn mockInternalLogger | ||
| whenever(mockSdkCore.createSingleThreadExecutorService(FLAGS_CLIENT_EXECUTOR_NAME)) doReturn | ||
| whenever(mockSdkCore.createSingleThreadExecutorService(org.mockito.kotlin.any())) doReturn |
| @Test | ||
| fun `M not block W hasFlags() { persistence still loading }`() { | ||
| // Given | ||
| val startTime = System.currentTimeMillis() |
| - "kotlinx.coroutines.flow.MutableStateFlow(com.datadog.android.flags.model.FlagsClientState)" | ||
| - "kotlinx.coroutines.flow.MutableStateFlow.asStateFlow()" |
| /** | ||
| * Observable interface for tracking client state changes. | ||
| * | ||
| * Provides three ways to observe state: | ||
| * - Synchronous: [StateObservable.getCurrentState] for immediate queries (Java-friendly) | ||
| * - Reactive: [StateObservable.flow] for coroutine-based updates (Kotlin) | ||
| * - Callback: [StateObservable.addListener] for traditional observers (Java-friendly) | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * // Synchronous | ||
| * val current = client.state.getCurrentState() | ||
| * | ||
| * // Reactive Flow | ||
| * client.state.flow.collect { state -> /* ... */ } |
| // region addListener / removeListener | ||
|
|
||
| @Test | ||
| fun `M notify listener W addListener() and notify`() { |
|
/merge |
|
View all feedbacks in Devflow UI.
This pull request is not mergeable according to GitHub. Common reasons include pending required checks, missing approvals, or merge conflicts — but it could also be blocked by other repository rules or settings.
tyler.potter@datadoghq.com unqueued this merge request |
|
Thanks. Just need one more stamp for the final change |
|
/remove |
|
View all feedbacks in Devflow UI.
|
Requested changes have been applied; reviewer is on PTO.
|
/merge |
|
View all feedbacks in Devflow UI.
The expected merge time in
Tests failed on this commit 1909853: What to do next?
|
Implements state observation API for FlagsClient: - Add FlagsClientState sealed class (NotReady, Reconciling, Ready, Stale, Error) - Add FlagsStateListener interface for state change callbacks - Add StateObservable interface bundling getCurrentState, addListener, removeListener, and Flow - Implement FlagsStateManager with ExecutorService for ordered state notifications - Add synchronized blocks for defensive thread safety - Expose state via client.state property - Add StateFlow support for Kotlin coroutines - Ensure Java compatibility - Update NoOpFlagsClient with Ready state - Add comprehensive tests PR #3025
Implements state observation API for FlagsClient: - Add FlagsClientState sealed class (NotReady, Reconciling, Ready, Stale, Error) - Add FlagsStateListener interface for state change callbacks - Add StateObservable interface bundling getCurrentState, addListener, removeListener, and Flow - Implement FlagsStateManager with ExecutorService for ordered state notifications - Add synchronized blocks for defensive thread safety - Expose state via client.state property - Add StateFlow support for Kotlin coroutines - Ensure Java compatibility - Update NoOpFlagsClient with Ready state - Add comprehensive tests PR #3025
What does this PR do?
Implements state management for
FlagsClientin thedd-sdk-android-flagsmodule, enabling applications and OpenFeature providers to observe client lifecycle states.Key Changes:
FlagsClientStateenum with states:NOT_READY,READY,RECONCILING,ERRORFlagsStateListenerinterface for receiving state change notificationsFlagsClientinterface with state management methods:getCurrentState(),addStateListener(),removeStateListener()FlagsStateChannelwrapper overDDCoreSubscriptionwith semantic notification methodsEvaluationsManagertoDatadogFlagsClientMotivation
This change is a prerequisite for implementing the OpenFeature provider's
observe()method. The OpenFeature specification requires providers to emit events (PROVIDER_READY, PROVIDER_RECONCILING, PROVIDER_ERROR) when their state changes.Without state tracking in the underlying
FlagsClient, the OpenFeature provider has no mechanism to know when flags are loaded, when context changes begin/complete, or when errors occur.Benefits:
observe()specificationAdditional Notes
Design Decisions:
Left out STALE state: The OpenFeature spec includes a
STALEstate for "cached flags may be outdated", but we removed it because there's no current mechanism to determine staleness. Added a note inFlagsClientStatedocs that this may be added in a future release.DDCoreSubscription with synchronization: Used the existing
DDCoreSubscriptionutility for listener management, wrapped withsynchronizedblocks to guarantee ordered delivery of state changes (critical forNOT_READY → RECONCILING → READYflows).FlagsStateChannel abstraction: Created a wrapper class with semantic methods (
notifyReady(),notifyReconciling(), etc.) to abstract DDCoreSubscription internals and improve code readability.State Transition Flow:
Thread Safety:
getCurrentState(): Lock-free atomic readaddStateListener()/removeStateListener(): Thread-safe viaDDCoreSubscriptionReview checklist (to be filled by reviewers)
Review checklist (to be filled by reviewers)