Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
b6796fc
Version 5.3.2-rc1
gthea Oct 29, 2025
075cb85
WIP fused
gthea Oct 31, 2025
492ee25
WIP standalone / parent module
gthea Oct 31, 2025
3778c1e
Fix
gthea Oct 31, 2025
9038ecc
Consumer rules
gthea Nov 3, 2025
fb4eaeb
Restore
gthea Nov 4, 2025
2de6066
Remove unnecessary code
gthea Nov 4, 2025
4cf4c11
Publish working
gthea Nov 5, 2025
f2481d4
Signing task
gthea Nov 6, 2025
015ef86
Change version; expose SplitLogLevel
gthea Nov 6, 2025
eb4e6fb
Rename internal log level
gthea Nov 6, 2025
dceacb4
Merge branch 'development' into fused
gthea Nov 10, 2025
0fddefc
Restore log level
gthea Nov 10, 2025
4cac101
Update Logger tests
gthea Nov 10, 2025
1e0c0a1
Rename internal modules
gthea Nov 10, 2025
b6eb501
Clean up build.gradle
gthea Nov 10, 2025
4b13de6
Clean up
gthea Nov 10, 2025
2f9315a
Remove .module
gthea Nov 10, 2025
ed961c9
lint fix
gthea Nov 11, 2025
e5801de
Remove project/; refactor Gradle scripts
gthea Nov 12, 2025
d2bf298
Remove group and version in submodules
gthea Nov 12, 2025
33f8395
Scope Kotlin version override
gthea Nov 12, 2025
f0d24fb
Diffuse script enhancements
gthea Nov 14, 2025
89d9c94
Pipeline fix
gthea Nov 14, 2025
9d3831f
Update diffuse script
gthea Nov 14, 2025
9685552
Pull remote branches
gthea Nov 14, 2025
4c95ae3
New script
gthea Nov 14, 2025
3cc2e8e
events module
gthea Nov 14, 2025
e0af4b4
WIP
gthea Nov 14, 2025
cd6ac82
Renaming
gthea Nov 14, 2025
c06fc13
SplitEventsConfig
gthea Nov 18, 2025
99fc8c8
WIP
gthea Nov 20, 2025
3f3a4bb
Merge branch 'FME-11237_baseline' into events-poc
gthea Nov 20, 2025
0361384
Events module and interfaces
gthea Nov 20, 2025
68458c8
Jacoco fixes
gthea Nov 20, 2025
c4788d6
Remove missing dir from Sonar config
gthea Nov 20, 2025
a777140
Remove class
gthea Nov 20, 2025
4b8d1e2
Fix submodule coverage
gthea Nov 25, 2025
8e754ea
Fix coverage collection
gthea Nov 25, 2025
5e8e94f
Simplify sonarqube workflow
gthea Nov 25, 2025
c95de14
Add unregister method
gthea Nov 25, 2025
5da029d
Add libraries property in sonar
gthea Nov 25, 2025
d81f18c
EventsManagerConfig builder
gthea Nov 25, 2025
a605c0e
EventsManagerCore
gthea Nov 25, 2025
d377345
Update Gradle
gthea Nov 25, 2025
8e5050f
Clarification comment
gthea Nov 25, 2025
7c41008
Enhanced sonarqube workflow
gthea Nov 26, 2025
0111c34
Fix issues
gthea Nov 26, 2025
90e2bae
API and events-domain modules
gthea Nov 26, 2025
52b9452
API and Events domain modules
gthea Nov 26, 2025
1c44c5c
EventMetadata implementation
gthea Nov 26, 2025
c7561dd
Modify events-domain README
gthea Nov 26, 2025
0585ebf
Remove stacktrace
gthea Nov 26, 2025
8faefa0
Merge branch 'FME-11237_baseline' into FME-11235
gthea Nov 26, 2025
88cdc6e
Update readme
gthea Nov 26, 2025
c3a4616
Merge branch 'FME-11235' into FME-11235_2
gthea Nov 26, 2025
f9975b2
Cleanup
gthea Nov 26, 2025
0715d65
Clean up
gthea Nov 26, 2025
e1a7566
Cleanup
gthea Nov 26, 2025
41b0921
Use jetbrains annotations for core events module
gthea Nov 26, 2025
172f354
Update jetbrains annotations version
gthea Nov 27, 2025
d61a5c3
Core events delivery functionality
gthea Nov 27, 2025
853b504
Fix javadoc
gthea Nov 27, 2025
0345060
Fix SQ issues
gthea Nov 27, 2025
22d8796
EventsManager provider
gthea Nov 27, 2025
6bbbaab
BG and main thread event delivery
gthea Nov 27, 2025
2c82d0a
Logging
gthea Nov 27, 2025
272e80e
More strict log message check
gthea Nov 27, 2025
67354a2
Remove unnecessary classes
gthea Nov 27, 2025
92236a2
EventsManagersTest
gthea Nov 27, 2025
a5c282b
Move some events files to api and events-domain
gthea Nov 27, 2025
0d6bd0b
Move events classes to events-domain and api modules
gthea Nov 27, 2025
ceeb858
WIP SplitEventsManager
gthea Nov 28, 2025
55d59b4
Fix import
gthea Nov 28, 2025
09424af
Fix unlimited events edge case
gthea Nov 28, 2025
a22aae4
Clean up
gthea Nov 28, 2025
be0c187
Clean up 2
gthea Nov 28, 2025
810e99a
Clean up 3
gthea Nov 28, 2025
3eeebe1
EventsManagerCore extension
gthea Nov 29, 2025
27f5530
Migrate events-domain logic to use new Core functionality
gthea Nov 29, 2025
574ab9d
EventsManagerCore edge case
gthea Nov 30, 2025
23c26f6
Remove stream usage
gthea Nov 30, 2025
6d0d44d
Refactor EventsManagerCore for readability
gthea Nov 30, 2025
6f828e3
Clean up
gthea Dec 2, 2025
de7f159
Fix SQ issues
gthea Dec 2, 2025
922eeac
WIP
gthea Nov 30, 2025
31dfb31
Rename sync internal events
gthea Dec 2, 2025
1642d0b
New tests
gthea Dec 2, 2025
f81d5c0
Merge branch 'FME-11237_baseline' into FME-11238-split-events_4
gthea Dec 4, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class EventsManagerCoordinator implements ISplitEventsManager, EventsMana
*/
private static final Set<SplitInternalEvent> SDK_SCOPED_EVENTS = EnumSet.of(
SplitInternalEvent.SPLITS_UPDATED,
SplitInternalEvent.SPLITS_FETCHED,
SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE,
SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE,
SplitInternalEvent.SPLIT_KILLED_NOTIFICATION,
SplitInternalEvent.RULE_BASED_SEGMENTS_UPDATED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,7 @@ public SplitEventExecutorResources getExecutorResources() {
@Override
public void notifyInternalEvent(SplitInternalEvent internalEvent) {
requireNonNull(internalEvent);

// Skip FETCHED events after SDK_READY to prevent unnecessary SDK_UPDATE triggers.
// TODO: This is temporary until *_FETCHED and *_UPDATED events are unified.
if ((internalEvent == SplitInternalEvent.SPLITS_FETCHED
|| internalEvent == SplitInternalEvent.MY_SEGMENTS_FETCHED)
&& eventAlreadyTriggered(SplitEvent.SDK_READY)) {
return;
}

// Notify the actual internal event
mEventsManager.notifyInternalEvent(internalEvent, null);

// Also notify the synthetic composite events for SDK_READY evaluation
notifySyntheticEventsIfNeeded(internalEvent);
}

/**
Expand All @@ -104,16 +91,7 @@ && eventAlreadyTriggered(SplitEvent.SDK_READY)) {
*/
public void notifyInternalEvent(SplitInternalEvent internalEvent, EventMetadata metadata) {
requireNonNull(internalEvent);

// Skip FETCHED events after SDK_READY
if ((internalEvent == SplitInternalEvent.SPLITS_FETCHED
|| internalEvent == SplitInternalEvent.MY_SEGMENTS_FETCHED)
&& eventAlreadyTriggered(SplitEvent.SDK_READY)) {
return;
}

mEventsManager.notifyInternalEvent(internalEvent, metadata);
notifySyntheticEventsIfNeeded(internalEvent);
}

@Override
Expand Down Expand Up @@ -143,32 +121,6 @@ public void destroy() {
mEventsManager.destroy();
}

/**
* Notifies the synthetic composite events based on the actual internal event.
* These synthetic events simplify the SDK_READY condition evaluation.
* The prerequisite configuration ensures SDK_READY_FROM_CACHE always fires before SDK_READY.
* <p>
* TODO: Remove this method once EventsManagerConfig is updated.
*/
private void notifySyntheticEventsIfNeeded(SplitInternalEvent internalEvent) {
switch (internalEvent) {
case SPLITS_UPDATED:
case SPLITS_FETCHED:
mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_SYNC_COMPLETE, null);
break;

case MY_SEGMENTS_UPDATED:
case MY_SEGMENTS_FETCHED:
case MY_LARGE_SEGMENTS_UPDATED:
mEventsManager.notifyInternalEvent(SplitInternalEvent.SEGMENTS_SYNC_COMPLETE, null);
break;

default:
// No synthetic event needed for other internal events
break;
}
}

private void startTimeoutThread(final int blockUntilReady) {
Thread timeoutThread = new Thread(new Runnable() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ static EventsManagerConfig<SplitEvent, SplitInternalEvent> create() {
cacheGroup.add(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE);

Set<SplitInternalEvent> syncGroup = new HashSet<>();
syncGroup.add(SplitInternalEvent.SPLITS_SYNC_COMPLETE);
syncGroup.add(SplitInternalEvent.SEGMENTS_SYNC_COMPLETE);
syncGroup.add(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE);
syncGroup.add(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE);

return EventsManagerConfig.<SplitEvent, SplitInternalEvent>builder()
.requireAll(SplitEvent.SDK_READY,
SplitInternalEvent.SPLITS_SYNC_COMPLETE,
SplitInternalEvent.SEGMENTS_SYNC_COMPLETE)
SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE,
SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE)

// SDK_READY_FROM_CACHE: OR of ANDs
// Fires when (cache group all done) OR (sync group all done)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,26 @@
package io.split.android.client.events;

/**
* Created by sarrubia on 4/6/18.
* Internal events used to track SDK initialization and data updates.
*/

public enum SplitInternalEvent {
// Cache loading events
MY_SEGMENTS_LOADED_FROM_STORAGE,
SPLITS_LOADED_FROM_STORAGE,
MY_SEGMENTS_FETCHED,
MY_SEGMENTS_UPDATED,
SPLITS_FETCHED,
SPLITS_UPDATED,
SDK_READY_TIMEOUT_REACHED,
SPLIT_KILLED_NOTIFICATION,
ATTRIBUTES_LOADED_FROM_STORAGE,
ENCRYPTION_MIGRATION_DONE,

// Data update events (fired only when data actually changed)
MY_SEGMENTS_UPDATED,
SPLITS_UPDATED,
MY_LARGE_SEGMENTS_UPDATED,
RULE_BASED_SEGMENTS_UPDATED,
SPLIT_KILLED_NOTIFICATION,

/**
* Synthetic event: fired when splits sync completes (either SPLITS_FETCHED or SPLITS_UPDATED).
* Used internally to simplify SDK_READY and SDK_READY_FROM_CACHE condition evaluation.
*/
SPLITS_SYNC_COMPLETE,
// Sync completion events (fired when sync completes, regardless of data change)
TARGETING_RULES_SYNC_COMPLETE,
MEMBERSHIPS_SYNC_COMPLETE,

/**
* Synthetic event: fired when segments sync completes (any of MY_SEGMENTS_FETCHED,
* MY_SEGMENTS_UPDATED, or MY_LARGE_SEGMENTS_UPDATED).
* Used internally to simplify SDK_READY and SDK_READY_FROM_CACHE condition evaluation.
*/
SEGMENTS_SYNC_COMPLETE,
// Other events
SDK_READY_TIMEOUT_REACHED,
}
39 changes: 21 additions & 18 deletions main/src/androidTest/java/tests/service/EventsManagerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import io.split.android.client.events.SplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
import io.split.android.client.service.executor.SplitTaskExecutorImpl;
import io.split.android.client.service.synchronizer.ThreadUtils;

public class EventsManagerTest {
@Test
Expand All @@ -37,7 +36,7 @@ public void testSdkUpdateSplits() throws InterruptedException {
}

@Test
public void testSdkFetchedUpdatedSplits() throws InterruptedException {
public void testSdkUpdateTriggersAfterReady() throws InterruptedException {

SplitClientConfig cfg = SplitClientConfig.builder().build();
SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorImpl(), cfg.blockUntilReady());
Expand All @@ -47,8 +46,10 @@ public void testSdkFetchedUpdatedSplits() throws InterruptedException {
TestingHelper.TestEventTask updateTask = TestingHelper.testTask(updateLatch);
eventManager.register(SplitEvent.SDK_UPDATE, updateTask);

eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_UPDATED);
eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_FETCHED);
// First make SDK_READY fire by completing sync
eventManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE);
eventManager.notifyInternalEvent(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE);
// Then trigger SDK_UPDATE with a data change
eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED);

updateLatch.await(5, TimeUnit.SECONDS);
Expand All @@ -57,7 +58,7 @@ public void testSdkFetchedUpdatedSplits() throws InterruptedException {
}

@Test
public void testSdkUpdatedFetchedSplits() throws InterruptedException {
public void testSdkUpdateDoesNotTriggerBeforeReady() throws InterruptedException {

SplitClientConfig cfg = SplitClientConfig.builder().build();
SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorImpl(), cfg.blockUntilReady());
Expand All @@ -67,12 +68,11 @@ public void testSdkUpdatedFetchedSplits() throws InterruptedException {
TestingHelper.TestEventTask updateTask = TestingHelper.testTask(updateLatch);
eventManager.register(SplitEvent.SDK_UPDATE, updateTask);

eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_UPDATED);
// Fire UPDATED before SDK_READY - should NOT trigger SDK_UPDATE due to prerequisite
eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED);
eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_FETCHED);

eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_UPDATED);

updateLatch.await(5, TimeUnit.SECONDS);
updateLatch.await(2, TimeUnit.SECONDS);

Assert.assertFalse(updateTask.onExecutedCalled);
}
Expand All @@ -98,7 +98,7 @@ public void testSdkUpdateSegments() throws InterruptedException {
}

@Test
public void testSdkFetchedUpdatedSegments() throws InterruptedException {
public void testSdkUpdateTriggersOnSegmentChange() throws InterruptedException {

SplitClientConfig cfg = SplitClientConfig.builder().build();
SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorImpl(), cfg.blockUntilReady());
Expand All @@ -108,8 +108,10 @@ public void testSdkFetchedUpdatedSegments() throws InterruptedException {
TestingHelper.TestEventTask updateTask = TestingHelper.testTask(updateLatch);
eventManager.register(SplitEvent.SDK_UPDATE, updateTask);

eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED);
eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_FETCHED);
// Make SDK_READY fire
eventManager.notifyInternalEvent(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE);
eventManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE);
// Then trigger SDK_UPDATE with a segment change
eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_UPDATED);

updateLatch.await(5, TimeUnit.SECONDS);
Expand All @@ -118,7 +120,7 @@ public void testSdkFetchedUpdatedSegments() throws InterruptedException {
}

@Test
public void testSdkUpdatedFetchedSegments() throws InterruptedException {
public void testSdkUpdateRequiresDataChange() throws InterruptedException {

SplitClientConfig cfg = SplitClientConfig.builder().build();
SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorImpl(), cfg.blockUntilReady());
Expand All @@ -128,13 +130,14 @@ public void testSdkUpdatedFetchedSegments() throws InterruptedException {
TestingHelper.TestEventTask updateTask = TestingHelper.testTask(updateLatch);
eventManager.register(SplitEvent.SDK_UPDATE, updateTask);

eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED);
eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_UPDATED);
eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_FETCHED);
// Make SDK_READY fire with only SYNC_COMPLETE events (no UPDATED)
eventManager.notifyInternalEvent(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE);
eventManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE);
// No UPDATED events fired

updateLatch.await(2, TimeUnit.SECONDS);

updateLatch.await(5, TimeUnit.SECONDS);

// SDK_UPDATE should NOT fire because no data actually changed
Assert.assertFalse(updateTask.onExecutedCalled);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ private void loadSplits() {
}
if (!content.equals(mLastContentLoaded)) {
mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE);
mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_FETCHED);
mEventsManager.notifyInternalEvent(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE);
mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED);
}
mLastContentLoaded = content;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.split.android.client.localhost.shared;

import androidx.annotation.VisibleForTesting;

import io.split.android.client.FlagSetsFilter;
import io.split.android.client.SplitClient;
import io.split.android.client.SplitClientConfig;
Expand Down Expand Up @@ -31,6 +33,7 @@ public class LocalhostSplitClientContainerImpl extends BaseSplitClientContainer
private final EventsManagerCoordinator mEventsManagerCoordinator;
private final SplitTaskExecutor mSplitTaskExecutor;
private final FlagSetsFilter mFlagSetsFilter;
private final SplitEventsManagerFactory mEventsManagerFactory;

public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory,
SplitClientConfig config,
Expand All @@ -42,6 +45,24 @@ public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory,
EventsManagerCoordinator eventsManagerCoordinator,
SplitTaskExecutor taskExecutor,
FlagSetsFilter flagSetsFilter) {
this(splitFactory, config, splitsStorage, splitParser, attributesManagerFactory,
attributesMerger, telemetryStorageProducer, eventsManagerCoordinator,
taskExecutor, flagSetsFilter,
new DefaultSplitEventsManagerFactory(taskExecutor, config));
}

@VisibleForTesting
LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory,
SplitClientConfig config,
SplitsStorage splitsStorage,
SplitParser splitParser,
AttributesManagerFactory attributesManagerFactory,
AttributesMerger attributesMerger,
TelemetryStorageProducer telemetryStorageProducer,
EventsManagerCoordinator eventsManagerCoordinator,
SplitTaskExecutor taskExecutor,
FlagSetsFilter flagSetsFilter,
SplitEventsManagerFactory eventsManagerFactory) {
mSplitFactory = splitFactory;
mConfig = config;
mSplitStorage = splitsStorage;
Expand All @@ -52,13 +73,14 @@ public LocalhostSplitClientContainerImpl(LocalhostSplitFactory splitFactory,
mEventsManagerCoordinator = eventsManagerCoordinator;
mSplitTaskExecutor = taskExecutor;
mFlagSetsFilter = flagSetsFilter;
mEventsManagerFactory = eventsManagerFactory;
}

@Override
protected void createNewClient(Key key) {
SplitEventsManager eventsManager = new SplitEventsManager(mSplitTaskExecutor, mConfig.blockUntilReady());
SplitEventsManager eventsManager = mEventsManagerFactory.create();
eventsManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_LOADED_FROM_STORAGE);
eventsManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_FETCHED);
eventsManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE);
eventsManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_UPDATED);

AttributesStorageImpl attributesStorage = new AttributesStorageImpl();
Expand Down Expand Up @@ -88,4 +110,19 @@ protected void createNewClient(Key key) {
public void destroy() {
// No-op
}

private static class DefaultSplitEventsManagerFactory implements SplitEventsManagerFactory {
private final SplitTaskExecutor mTaskExecutor;
private final int mBlockUntilReady;

DefaultSplitEventsManagerFactory(SplitTaskExecutor taskExecutor, SplitClientConfig config) {
mTaskExecutor = taskExecutor;
mBlockUntilReady = config.blockUntilReady();
}

@Override
public SplitEventsManager create() {
return new SplitEventsManager(mTaskExecutor, mBlockUntilReady);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.split.android.client.localhost.shared;

import io.split.android.client.events.SplitEventsManager;

/**
* Factory interface for creating SplitEventsManager instances.
* Package-local interface to allow testing by injecting mock implementations.
*/
interface SplitEventsManagerFactory {
/**
* Creates a new SplitEventsManager instance.
*
* @return a new SplitEventsManager instance
*/
SplitEventsManager create();
}

Loading
Loading