Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 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
043d34d
Events domain fix
gthea Dec 5, 2025
75249b7
Tentative main fix
gthea Dec 5, 2025
d4942e2
Sort events dependencies
gthea Dec 5, 2025
c480ec6
Remove unneccessary if
gthea Dec 5, 2025
84502f3
Merge branch 'FME-11238-split-events_2' into FME-11391-fixes-comp
gthea Dec 5, 2025
6e6d3ff
Delete class
gthea Dec 5, 2025
b481952
Fix bad merge
gthea Dec 5, 2025
d406c49
Fixes
gthea Dec 5, 2025
09f5bbb
Events module readme
gthea Dec 5, 2025
dc205a7
Update test due to prereq
gthea Dec 5, 2025
12dcc26
Fix test
gthea Dec 5, 2025
53bedfb
Remove unused method; update tests
gthea Dec 5, 2025
1659f5c
Update tests
gthea Dec 5, 2025
5a079a6
Refactor and simplification
gthea Dec 9, 2025
773a776
Merge branch 'FME-11391-fixes' into FME-11391-fixes-comp
gthea Dec 9, 2025
9d227fb
Re add all events
gthea Dec 9, 2025
aa270b6
Fixes
gthea Dec 9, 2025
35f97c0
Remove diff
gthea Dec 9, 2025
10d73a5
Clean up
gthea Dec 9, 2025
bdab58f
SQ issues
gthea Dec 9, 2025
5a8f3da
New tests
gthea Dec 9, 2025
a585c86
New API for events
gthea Dec 9, 2025
5cb9ad0
Update SplitEventTask JavaDoc
gthea Dec 9, 2025
c1dc8d9
Remove unnecessary JavaDoc
gthea Dec 9, 2025
6bc27c4
Nullability annotations
gthea Dec 9, 2025
79232c4
Integration test for Thread context
gthea Dec 9, 2025
d34d244
Merge branch 'FME-11237_baseline' into FME-11391-new-api
gthea Dec 9, 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
100 changes: 98 additions & 2 deletions api/src/main/java/io/split/android/client/events/SplitEventTask.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,113 @@
package io.split.android.client.events;

import androidx.annotation.Nullable;

import io.split.android.client.SplitClient;
import io.split.android.client.api.EventMetadata;

/**
* Created by sarrubia on 3/26/18.
* Base class for handling Split SDK events.
* <p>
* Extend this class and override the methods you need to handle specific SDK events.
* You can implement both the metadata-enabled and versions of the methods;
* if both are implemented, both will be called (metadata version first).
* <p>
* <b>Threading:</b>
* <ul>
* <li>{@code onPostExecution} methods are called on a background thread (faster, executed immediately)</li>
* <li>{@code onPostExecutionView} methods are called on the main/UI thread (queued on main looper)</li>
* </ul>
* <p>
* <b>Metadata:</b>
* <ul>
* <li>Metadata-enabled methods receive {@link EventMetadata} containing event-specific information</li>
* <li>Metadata may be {@code null} for some events</li>
* <li>If you only need metadata, implement the metadata version; if you need backward compatibility,
* implement both versions</li>
* </ul>
* <p>
* Example usage:
* <pre>{@code
* client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
* @Override
* public void onPostExecution(SplitClient client, EventMetadata metadata) {
* List<String> updatedFlags = (List<String>) metadata.get("updatedFlags");
* // Handle update with metadata
* }
*
* @Override
* public void onPostExecution(SplitClient client) {
* // Legacy handling (also called if both are implemented)
* }
* });
* }</pre>
*/

public class SplitEventTask {
/**
* Called when an event occurs, executed on a background thread.
* <p>
* Override this method to handle events on a background thread without metadata.
* This method is executed immediately and is faster than {@link #onPostExecutionView(SplitClient)}.
*
* @param client the Split client instance
* @throws SplitEventTaskMethodNotImplementedException if not overridden (default behavior)
*/
public void onPostExecution(SplitClient client) {
throw new SplitEventTaskMethodNotImplementedException();
}

/**
* Called when an event occurs, executed on the main/UI thread.
* <p>
* Override this method to handle events on the main thread without metadata.
* Use this when you need to update UI components.
* <p>
* Note: This method is queued on the main looper, so execution may be delayed
* compared to {@link #onPostExecution(SplitClient)}.
*
* @param client the Split client instance
* @throws SplitEventTaskMethodNotImplementedException if not overridden (default behavior)
*/
public void onPostExecutionView(SplitClient client) {
throw new SplitEventTaskMethodNotImplementedException();
}

/**
* Called when an event occurs with metadata, executed on a background thread.
* <p>
* Override this method to handle events on a background thread with access to event metadata.
* The metadata contains event-specific information such as updated flag names for SDK_UPDATE events.
* This method is executed immediately and is faster than {@link #onPostExecutionView(SplitClient, EventMetadata)}.
* <p>
* If both this method and {@link #onPostExecution(SplitClient)} are implemented,
* both will be called (this method first).
*
* @param client the Split client instance
* @param metadata the event metadata, may be {@code null} for some events
* @throws SplitEventTaskMethodNotImplementedException if not overridden (default behavior)
*/
public void onPostExecution(SplitClient client, @Nullable EventMetadata metadata) {
throw new SplitEventTaskMethodNotImplementedException();
}

/**
* Called when an event occurs with metadata, executed on the main/UI thread.
* <p>
* Override this method to handle events on the main thread with access to event metadata.
* The metadata contains event-specific information such as updated flag names for SDK_UPDATE events.
* Use this when you need to update UI components based on event metadata.
* <p>
* Note: This method is queued on the main looper, so execution may be delayed
* compared to {@link #onPostExecution(SplitClient, EventMetadata)}.
* <p>
* If both this method and {@link #onPostExecutionView(SplitClient)} are implemented,
* both will be called (this method first).
*
* @param client the Split client instance
* @param metadata the event metadata, may be {@code null} for some events
* @throws SplitEventTaskMethodNotImplementedException if not overridden (default behavior)
*/
public void onPostExecutionView(SplitClient client, @Nullable EventMetadata metadata) {
throw new SplitEventTaskMethodNotImplementedException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,30 +145,66 @@ public void run() {
}

private EventHandler<SplitEvent, EventMetadata> createBackgroundHandler(final SplitEventTask task) {
return new EventHandler<SplitEvent, EventMetadata>() {
return createEventHandler(task, "background", new TaskMethodCaller() {
@Override
public void handle(SplitEvent event, EventMetadata metadata) {
try {
task.onPostExecution(mResources.getSplitClient());
} catch (SplitEventTaskMethodNotImplementedException e) {
// Method not implemented by client, ignore
} catch (Exception e) {
Logger.e("Error executing background event task: " + e.getMessage());
}
public void callWithMetadata(EventMetadata metadata) {
task.onPostExecution(mResources.getSplitClient(), metadata);
}
};

@Override
public void callWithoutMetadata() {
task.onPostExecution(mResources.getSplitClient());
}
});
}

private EventHandler<SplitEvent, EventMetadata> createMainThreadHandler(final SplitEventTask task) {
return createEventHandler(task, "main thread", new TaskMethodCaller() {
@Override
public void callWithMetadata(EventMetadata metadata) {
task.onPostExecutionView(mResources.getSplitClient(), metadata);
}

@Override
public void callWithoutMetadata() {
task.onPostExecutionView(mResources.getSplitClient());
}
});
}

/**
* Helper interface for calling task methods.
*/
private interface TaskMethodCaller {
void callWithMetadata(EventMetadata metadata) throws Exception;
void callWithoutMetadata() throws Exception;
}

private EventHandler<SplitEvent, EventMetadata> createEventHandler(
final SplitEventTask task,
final String threadType,
final TaskMethodCaller caller) {
return new EventHandler<SplitEvent, EventMetadata>() {
@Override
public void handle(SplitEvent event, EventMetadata metadata) {
executeTaskMethod(metadata, true, threadType, caller);
executeTaskMethod(metadata, false, threadType, caller);
}

private void executeTaskMethod(EventMetadata metadata, boolean withMetadata, String threadType, TaskMethodCaller caller) {
try {
task.onPostExecutionView(mResources.getSplitClient());
if (withMetadata) {
caller.callWithMetadata(metadata);
} else {
caller.callWithoutMetadata();
}
} catch (SplitEventTaskMethodNotImplementedException e) {
// Method not implemented by client, ignore
} catch (Exception e) {
Logger.e("Error executing main thread event task: " + e.getMessage());
String errorPrefix = withMetadata
? "Error executing " + threadType + " event task (with metadata): "
: "Error executing " + threadType + " event task: ";
Logger.e(errorPrefix + e.getMessage());
}
}
};
Expand Down
100 changes: 100 additions & 0 deletions main/src/androidTest/java/tests/service/EventsManagerTest.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
package tests.service;

import android.os.Looper;

import org.junit.Assert;
import org.junit.Test;

import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import fake.SplitEventExecutorResourcesMock;
import helper.TestingHelper;
import io.split.android.client.SplitClient;
import io.split.android.client.SplitClientConfig;
import io.split.android.client.api.EventMetadata;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.client.events.SplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
import io.split.android.client.events.metadata.EventMetadataHelpers;
import io.split.android.client.service.executor.SplitTaskExecutorImpl;

public class EventsManagerTest {
Expand Down Expand Up @@ -232,4 +241,95 @@ public void testTimeoutMySegmentsUpdated() throws InterruptedException {
Assert.assertFalse(updateTask.onExecutedCalled);
Assert.assertTrue(timeoutTask.onExecutedCalled);
}

@Test
public void testAllFourCallbackMethodsAreCalledWithCorrectThreadContext() throws InterruptedException {
SplitClientConfig cfg = SplitClientConfig.builder().build();
SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorImpl(), cfg.blockUntilReady());
eventManager.setExecutionResources(new SplitEventExecutorResourcesMock());

CountDownLatch readyLatch = new CountDownLatch(1);
CountDownLatch allCalledLatch = new CountDownLatch(4); // Expect 4 calls

AtomicBoolean backgroundMetadataCalled = new AtomicBoolean(false);
AtomicBoolean backgroundLegacyCalled = new AtomicBoolean(false);
AtomicBoolean mainThreadMetadataCalled = new AtomicBoolean(false);
AtomicBoolean mainThreadLegacyCalled = new AtomicBoolean(false);

AtomicBoolean backgroundMetadataOnMainThread = new AtomicBoolean(true); // Should be false
AtomicBoolean backgroundLegacyOnMainThread = new AtomicBoolean(true); // Should be false
AtomicBoolean mainThreadMetadataOnMainThread = new AtomicBoolean(false); // Should be true
AtomicBoolean mainThreadLegacyOnMainThread = new AtomicBoolean(false); // Should be true

AtomicReference<EventMetadata> backgroundMetadata = new AtomicReference<>();
AtomicReference<EventMetadata> mainThreadMetadata = new AtomicReference<>();

// Wait for SDK_READY first
eventManager.register(SplitEvent.SDK_READY, new SplitEventTask() {
@Override
public void onPostExecutionView(SplitClient client) {
readyLatch.countDown();
}
});

// Register a task that implements ALL FOUR methods
eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
@Override
public void onPostExecution(SplitClient client, EventMetadata metadata) {
backgroundMetadataCalled.set(true);
backgroundMetadataOnMainThread.set(Looper.myLooper() == Looper.getMainLooper());
backgroundMetadata.set(metadata);
allCalledLatch.countDown();
}

@Override
public void onPostExecution(SplitClient client) {
backgroundLegacyCalled.set(true);
backgroundLegacyOnMainThread.set(Looper.myLooper() == Looper.getMainLooper());
allCalledLatch.countDown();
}

@Override
public void onPostExecutionView(SplitClient client, EventMetadata metadata) {
mainThreadMetadataCalled.set(true);
mainThreadMetadataOnMainThread.set(Looper.myLooper() == Looper.getMainLooper());
mainThreadMetadata.set(metadata);
allCalledLatch.countDown();
}

@Override
public void onPostExecutionView(SplitClient client) {
mainThreadLegacyCalled.set(true);
mainThreadLegacyOnMainThread.set(Looper.myLooper() == Looper.getMainLooper());
allCalledLatch.countDown();
}
});

// Make SDK_READY fire
eventManager.notifyInternalEvent(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE);
eventManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE);
Assert.assertTrue("SDK_READY should fire", readyLatch.await(5, TimeUnit.SECONDS));

// Trigger SDK_UPDATE with metadata
EventMetadata metadata = EventMetadataHelpers.createUpdatedFlagsMetadata(
Arrays.asList("flag1", "flag2"));
eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED, metadata);

Assert.assertTrue("All four callbacks should be called", allCalledLatch.await(5, TimeUnit.SECONDS));

Assert.assertTrue("Background metadata method should be called", backgroundMetadataCalled.get());
Assert.assertTrue("Background legacy method should be called", backgroundLegacyCalled.get());
Assert.assertTrue("Main thread metadata method should be called", mainThreadMetadataCalled.get());
Assert.assertTrue("Main thread legacy method should be called", mainThreadLegacyCalled.get());

Assert.assertFalse("Background metadata method should NOT run on main thread", backgroundMetadataOnMainThread.get());
Assert.assertFalse("Background legacy method should NOT run on main thread", backgroundLegacyOnMainThread.get());
Assert.assertTrue("Main thread metadata method SHOULD run on main thread", mainThreadMetadataOnMainThread.get());
Assert.assertTrue("Main thread legacy method SHOULD run on main thread", mainThreadLegacyOnMainThread.get());

Assert.assertNotNull("Background metadata should not be null", backgroundMetadata.get());
Assert.assertTrue("Background metadata should contain updatedFlags", backgroundMetadata.get().containsKey("updatedFlags"));
Assert.assertNotNull("Main thread metadata should not be null", mainThreadMetadata.get());
Assert.assertTrue("Main thread metadata should contain updatedFlags", mainThreadMetadata.get().containsKey("updatedFlags"));
}
}
Loading
Loading