diff --git a/api/src/main/java/io/split/android/client/events/SplitEventTask.java b/api/src/main/java/io/split/android/client/events/SplitEventTask.java
index 5a5dd6db9..f880e0fe1 100644
--- a/api/src/main/java/io/split/android/client/events/SplitEventTask.java
+++ b/api/src/main/java/io/split/android/client/events/SplitEventTask.java
@@ -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.
+ *
+ * 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).
+ *
+ * Threading:
+ *
+ * - {@code onPostExecution} methods are called on a background thread (faster, executed immediately)
+ * - {@code onPostExecutionView} methods are called on the main/UI thread (queued on main looper)
+ *
+ *
+ * Metadata:
+ *
+ * - Metadata-enabled methods receive {@link EventMetadata} containing event-specific information
+ * - Metadata may be {@code null} for some events
+ * - If you only need metadata, implement the metadata version; if you need backward compatibility,
+ * implement both versions
+ *
+ *
+ * Example usage:
+ *
{@code
+ * client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ * @Override
+ * public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ * List updatedFlags = (List) metadata.get("updatedFlags");
+ * // Handle update with metadata
+ * }
+ *
+ * @Override
+ * public void onPostExecution(SplitClient client) {
+ * // Legacy handling (also called if both are implemented)
+ * }
+ * });
+ * }
*/
-
public class SplitEventTask {
+ /**
+ * Called when an event occurs, executed on a background thread.
+ *
+ * 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.
+ *
+ * Override this method to handle events on the main thread without metadata.
+ * Use this when you need to update UI components.
+ *
+ * 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.
+ *
+ * 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)}.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Note: This method is queued on the main looper, so execution may be delayed
+ * compared to {@link #onPostExecution(SplitClient, EventMetadata)}.
+ *
+ * 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();
+ }
}
diff --git a/events-domain/src/main/java/io/split/android/client/events/SplitEventsManager.java b/events-domain/src/main/java/io/split/android/client/events/SplitEventsManager.java
index 3dca17c88..cc417efe2 100644
--- a/events-domain/src/main/java/io/split/android/client/events/SplitEventsManager.java
+++ b/events-domain/src/main/java/io/split/android/client/events/SplitEventsManager.java
@@ -145,30 +145,66 @@ public void run() {
}
private EventHandler createBackgroundHandler(final SplitEventTask task) {
- return new EventHandler() {
+ 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 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 createEventHandler(
+ final SplitEventTask task,
+ final String threadType,
+ final TaskMethodCaller caller) {
return new EventHandler() {
@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());
}
}
};
diff --git a/main/src/androidTest/java/tests/service/EventsManagerTest.java b/main/src/androidTest/java/tests/service/EventsManagerTest.java
index 6462168bc..0ff18385a 100644
--- a/main/src/androidTest/java/tests/service/EventsManagerTest.java
+++ b/main/src/androidTest/java/tests/service/EventsManagerTest.java
@@ -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 {
@@ -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 backgroundMetadata = new AtomicReference<>();
+ AtomicReference 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"));
+ }
}
diff --git a/main/src/test/java/io/split/android/client/events/EventsManagerTest.java b/main/src/test/java/io/split/android/client/events/EventsManagerTest.java
index d4a1e1977..5b4643f3a 100644
--- a/main/src/test/java/io/split/android/client/events/EventsManagerTest.java
+++ b/main/src/test/java/io/split/android/client/events/EventsManagerTest.java
@@ -1,6 +1,8 @@
package io.split.android.client.events;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;
@@ -11,13 +13,17 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
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.executors.SplitEventExecutorResources;
+import io.split.android.client.events.metadata.EventMetadataHelpers;
import io.split.android.fake.SplitTaskExecutorStub;
public class EventsManagerTest {
@@ -266,4 +272,176 @@ private static void execute(boolean shouldStop, long intervalExecutionTime, long
}
}
}
+
+ @Test
+ public void sdkUpdateWithMetadataCallsMetadataMethod() throws InterruptedException {
+ SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0);
+ CountDownLatch readyLatch = new CountDownLatch(1);
+ CountDownLatch updateLatch = new CountDownLatch(1);
+ AtomicReference receivedMetadata = new AtomicReference<>();
+
+ waitForSdkReady(eventManager, readyLatch);
+
+ eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ @Override
+ public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ receivedMetadata.set(metadata);
+ updateLatch.countDown();
+ }
+ });
+
+ EventMetadata metadata = createTestMetadata();
+ triggerSdkUpdateWithMetadata(eventManager, metadata);
+
+ boolean updateAwait = updateLatch.await(3, TimeUnit.SECONDS);
+ assertTrue("SDK_UPDATE callback should be called", updateAwait);
+ assertNotNull("Metadata should not be null", receivedMetadata.get());
+ assertTrue("Metadata should contain updatedFlags", receivedMetadata.get().containsKey("updatedFlags"));
+ }
+
+ @Test
+ public void sdkUpdateWithMetadataCallsMetadataMethodOnMainThread() throws InterruptedException {
+ SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0);
+ CountDownLatch readyLatch = new CountDownLatch(1);
+ CountDownLatch updateLatch = new CountDownLatch(1);
+ AtomicReference receivedMetadata = new AtomicReference<>();
+
+ waitForSdkReady(eventManager, readyLatch);
+
+ eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ @Override
+ public void onPostExecutionView(SplitClient client, EventMetadata metadata) {
+ receivedMetadata.set(metadata);
+ updateLatch.countDown();
+ }
+ });
+
+ EventMetadata metadata = createTestMetadata();
+ triggerSdkUpdateWithMetadata(eventManager, metadata);
+
+ boolean updateAwait = updateLatch.await(3, TimeUnit.SECONDS);
+ assertTrue("SDK_UPDATE callback should be called on main thread", updateAwait);
+ assertNotNull("Metadata should not be null", receivedMetadata.get());
+ assertTrue("Metadata should contain updatedFlags", receivedMetadata.get().containsKey("updatedFlags"));
+ }
+
+ @Test
+ public void sdkUpdateCallsLegacyMethodWhenOnlyLegacyImplemented() throws InterruptedException {
+ SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0);
+ CountDownLatch readyLatch = new CountDownLatch(1);
+ CountDownLatch updateLatch = new CountDownLatch(1);
+ final boolean[] nonMetadataMethodCalled = {false};
+
+ waitForSdkReady(eventManager, readyLatch);
+
+ eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ @Override
+ public void onPostExecution(SplitClient client) {
+ nonMetadataMethodCalled[0] = true;
+ updateLatch.countDown();
+ }
+ });
+
+ EventMetadata metadata = createTestMetadata();
+ triggerSdkUpdateWithMetadata(eventManager, metadata);
+
+ boolean updateAwait = updateLatch.await(3, TimeUnit.SECONDS);
+ assertTrue("SDK_UPDATE callback should be called", updateAwait);
+ assertTrue("Legacy method should be called", nonMetadataMethodCalled[0]);
+ }
+
+ @Test
+ public void sdkUpdateCallsBothMethodsWhenBothImplemented() throws InterruptedException {
+ SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0);
+ CountDownLatch readyLatch = new CountDownLatch(1);
+ CountDownLatch bothCalledLatch = new CountDownLatch(2);
+ final boolean[] metadataMethodCalled = {false};
+ final boolean[] legacyMethodCalled = {false};
+ AtomicReference receivedMetadata = new AtomicReference<>();
+
+ waitForSdkReady(eventManager, readyLatch);
+
+ eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ @Override
+ public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ metadataMethodCalled[0] = true;
+ receivedMetadata.set(metadata);
+ bothCalledLatch.countDown();
+ }
+
+ @Override
+ public void onPostExecution(SplitClient client) {
+ legacyMethodCalled[0] = true;
+ bothCalledLatch.countDown();
+ }
+ });
+
+ EventMetadata metadata = createTestMetadata();
+ triggerSdkUpdateWithMetadata(eventManager, metadata);
+
+ boolean bothCalled = bothCalledLatch.await(3, TimeUnit.SECONDS);
+ assertTrue("Both callbacks should be called", bothCalled);
+ assertTrue("Metadata method should be called", metadataMethodCalled[0]);
+ assertTrue("Legacy method should also be called", legacyMethodCalled[0]);
+ assertNotNull("Metadata should be passed to metadata method", receivedMetadata.get());
+ assertTrue("Metadata should contain updatedFlags", receivedMetadata.get().containsKey("updatedFlags"));
+ }
+
+ @Test
+ public void sdkReadyFromCacheCallsBothMethodsWhenBothImplemented() throws InterruptedException {
+ SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0);
+
+ CountDownLatch bothCalledLatch = new CountDownLatch(2); // Expect 2 calls
+ final boolean[] metadataMethodCalled = {false};
+ final boolean[] legacyMethodCalled = {false};
+
+ // Register a task that implements both versions
+ eventManager.register(SplitEvent.SDK_READY_FROM_CACHE, new SplitEventTask() {
+ @Override
+ public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ metadataMethodCalled[0] = true;
+ bothCalledLatch.countDown();
+ }
+
+ @Override
+ public void onPostExecution(SplitClient client) {
+ legacyMethodCalled[0] = true;
+ bothCalledLatch.countDown();
+ }
+ });
+
+ // Trigger SDK_READY_FROM_CACHE
+ eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_LOADED_FROM_STORAGE);
+ eventManager.notifyInternalEvent(SplitInternalEvent.MY_SEGMENTS_LOADED_FROM_STORAGE);
+ eventManager.notifyInternalEvent(SplitInternalEvent.ATTRIBUTES_LOADED_FROM_STORAGE);
+ eventManager.notifyInternalEvent(SplitInternalEvent.ENCRYPTION_MIGRATION_DONE);
+
+ boolean bothCalled = bothCalledLatch.await(3, TimeUnit.SECONDS);
+ assertTrue("Both callbacks should be called", bothCalled);
+ assertTrue("Metadata method should be called", metadataMethodCalled[0]);
+ assertTrue("Legacy method should also be called", legacyMethodCalled[0]);
+ }
+
+ private void waitForSdkReady(SplitEventsManager eventManager, CountDownLatch readyLatch) throws InterruptedException {
+ eventManager.register(SplitEvent.SDK_READY, new SplitEventTask() {
+ @Override
+ public void onPostExecutionView(SplitClient client) {
+ readyLatch.countDown();
+ }
+ });
+
+ eventManager.notifyInternalEvent(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE);
+ eventManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE);
+ boolean readyAwait = readyLatch.await(3, TimeUnit.SECONDS);
+ assertTrue("SDK_READY should be triggered", readyAwait);
+ }
+
+ private static EventMetadata createTestMetadata() {
+ return EventMetadataHelpers.createUpdatedFlagsMetadata(
+ Arrays.asList("flag1", "flag2"));
+ }
+
+ private static void triggerSdkUpdateWithMetadata(SplitEventsManager eventManager, EventMetadata metadata) {
+ eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED, metadata);
+ }
}
diff --git a/main/src/test/java/io/split/android/client/events/SplitEventTaskMetadataTest.java b/main/src/test/java/io/split/android/client/events/SplitEventTaskMetadataTest.java
new file mode 100644
index 000000000..03b125ae2
--- /dev/null
+++ b/main/src/test/java/io/split/android/client/events/SplitEventTaskMetadataTest.java
@@ -0,0 +1,123 @@
+package io.split.android.client.events;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import io.split.android.client.SplitClient;
+import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadataHelpers;
+
+public class SplitEventTaskMetadataTest {
+
+ @Mock
+ private SplitClient mClient;
+
+ @Mock
+ private EventMetadata mMetadata;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.openMocks(this);
+ }
+
+ @Test
+ public void onPostExecutionWithMetadataThrowsExceptionWhenNotImplemented() {
+ SplitEventTask task = new SplitEventTask();
+
+ assertThrows(SplitEventTaskMethodNotImplementedException.class, () -> {
+ task.onPostExecution(mClient, mMetadata);
+ });
+ }
+
+ @Test
+ public void onPostExecutionViewWithMetadataThrowsExceptionWhenNotImplemented() {
+ SplitEventTask task = new SplitEventTask();
+
+ assertThrows(SplitEventTaskMethodNotImplementedException.class, () -> {
+ task.onPostExecutionView(mClient, mMetadata);
+ });
+ }
+
+ @Test
+ public void onPostExecutionWithMetadataCanBeOverridden() {
+ EventMetadata metadata = EventMetadataHelpers.createUpdatedFlagsMetadata(
+ java.util.Arrays.asList("flag1", "flag2"));
+
+ SplitEventTask task = new SplitEventTask() {
+ @Override
+ public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ // Overridden implementation
+ }
+ };
+
+ // Should not throw exception
+ task.onPostExecution(mClient, metadata);
+ }
+
+ @Test
+ public void onPostExecutionViewWithMetadataCanBeOverridden() {
+ EventMetadata metadata = EventMetadataHelpers.createCacheReadyMetadata(1234567890L, false);
+
+ SplitEventTask task = new SplitEventTask() {
+ @Override
+ public void onPostExecutionView(SplitClient client, EventMetadata metadata) {
+ // Overridden implementation
+ }
+ };
+
+ // Should not throw exception
+ task.onPostExecutionView(mClient, metadata);
+ }
+
+ @Test
+ public void onPostExecutionWithMetadataReceivesCorrectParameters() {
+ EventMetadata expectedMetadata = EventMetadataHelpers.createUpdatedFlagsMetadata(
+ java.util.Arrays.asList("flag1", "flag2"));
+
+ final boolean[] metadataReceived = {false};
+ final boolean[] hasUpdatedFlags = {false};
+
+ SplitEventTask task = new SplitEventTask() {
+ @Override
+ public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ metadataReceived[0] = metadata != null;
+ hasUpdatedFlags[0] = metadata != null && metadata.containsKey("updatedFlags");
+ }
+ };
+
+ task.onPostExecution(mClient, expectedMetadata);
+
+ assertTrue("Metadata should be received", metadataReceived[0]);
+ assertTrue("Metadata should contain updatedFlags", hasUpdatedFlags[0]);
+ }
+
+ @Test
+ public void onPostExecutionViewWithMetadataReceivesCorrectParameters() {
+ EventMetadata expectedMetadata = EventMetadataHelpers.createCacheReadyMetadata(1234567890L, false);
+
+ final boolean[] metadataReceived = {false};
+ final boolean[] hasTimestamp = {false};
+ final boolean[] hasFreshInstall = {false};
+
+ SplitEventTask task = new SplitEventTask() {
+ @Override
+ public void onPostExecutionView(SplitClient client, EventMetadata metadata) {
+ metadataReceived[0] = metadata != null;
+ hasTimestamp[0] = metadata != null && metadata.containsKey("lastUpdateTimestamp");
+ hasFreshInstall[0] = metadata != null && metadata.containsKey("freshInstall");
+ }
+ };
+
+ task.onPostExecutionView(mClient, expectedMetadata);
+
+ assertTrue("Metadata should be received", metadataReceived[0]);
+ assertTrue("Metadata should contain lastUpdateTimestamp", hasTimestamp[0]);
+ assertTrue("Metadata should contain freshInstall", hasFreshInstall[0]);
+ }
+}
+