Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: split event listeners #26

Merged
merged 3 commits into from
Jul 17, 2024
Merged

feat: split event listeners #26

merged 3 commits into from
Jul 17, 2024

Conversation

gastonfournier
Copy link
Contributor

This allows us to create a coroutine only when there's an action defined. Previously, we were using an empty implementation but the coroutine was still executing on every event.

@@ -52,8 +60,9 @@ class DefaultUnleash(
private val unleashConfig: UnleashConfig,
unleashContext: UnleashContext = UnleashContext(),
cacheImpl: ToggleCache = InMemoryToggleCache(),
eventListeners: List<UnleashEventListener> = emptyList(),
private val lifecycle: Lifecycle = getLifecycle(androidContext)
eventListeners: List<UnleashListener> = emptyList(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the ability of sending multiple event listeners instead of just one

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, Both List<UnleashEventListener> and List<UnleashListener>, allow you to send more than one though? They accept a list of listeners. So the real change here is a rename of the Interface?

Copy link
Contributor Author

@gastonfournier gastonfournier Jul 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, previously it was just an event listener but the change from 1 to many was done in a previous PR, this comment is stale :D currently, only changing the interface

private val fetcher: UnleashFetcher?
private val networkStatusHelper = NetworkStatusHelper(androidContext)
private val impressionEventsFlow = MutableSharedFlow<ImpressionEvent>(
replay = 1,
extraBufferCapacity = 1000,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this is too much, or maybe not. This is just to make sure we don't drop impression events, but we also protect the memory.

Replay turns out to be useful for predictability, because sometimes the listeners don't get to register before the first event happens (same as with isReady flow). Arguably, maybe a replay is not needed for impression events

private val impressionEventsFlow = MutableSharedFlow<ImpressionEvent>(
replay = 1,
extraBufferCapacity = 1000,
onBufferOverflow = BufferOverflow.DROP_OLDEST
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may miss some updates if you're not fast enough

Comment on lines 114 to 115
eventListeners: List<UnleashListener> = emptyList(),
bootstrap: List<Toggle> = emptyList()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to send these as part of the start method. In other proxy SDKs they're part of the configuration, but I feel there's no point in keeping the toggles in memory (as config live span is tied to the application life span). Eventually, when toggles are fetched, we no longer need to reference the original bootstrapped toggles and having them here instead than in the config object means that the GC will collect them

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a nice consideration

companion object {
private const val TAG = "ObservableCache"
}

private var events = MutableSharedFlow<UnleashState>()
private var newStateEventFlow = MutableSharedFlow<UnleashState>(
replay = 1,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes the cache more predictable. During testing I noticed that the coroutine that was adding a listener to the cache: https://github.com/Unleash/UnleashAndroidSdk/blob/3da55190599b660be58797075410dcc6f69e017b/unleashandroidsdk/src/main/java/io/getunleash/android/DefaultUnleash.kt#L246-L251

was executing after the first new state arrived (this is particularly true when using bootstraped toggles as they are written immediately to the cache). This caused the ready listener to miss the ready event.

With replay = 1 in place, even in this situation, the listener that arrives later will be able to see the first UnleashState received, despite the execution order.

Particularly for state, I believe having the latest always available even for new subscribers is important.

We are also not having a queue of states, just keep the latest and drop the oldest as soon as new states arrive

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me

@gastonfournier gastonfournier changed the base branch from main to impression-events July 17, 2024 09:17
@gastonfournier gastonfournier changed the base branch from impression-events to main July 17, 2024 09:18
Copy link
Member

@chriswk chriswk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

@gastonfournier gastonfournier merged commit f9e1213 into main Jul 17, 2024
1 check passed
@gastonfournier gastonfournier deleted the split-events branch July 17, 2024 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants