diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml
index f6e2d570b..94a0bf3c3 100644
--- a/.github/workflows/sonarqube.yml
+++ b/.github/workflows/sonarqube.yml
@@ -1,6 +1,11 @@
name: SonarCloud Analysis
on:
+ push:
+ branches:
+ - master
+ - development
+ - '*_baseline'
pull_request:
branches:
- '*'
@@ -59,8 +64,13 @@ jobs:
echo "=== Verifying Build Artifacts for SonarQube ==="
echo ""
+ # Dynamically get modules from settings.gradle (extract module names from "include ':modulename'" lines)
+ MODULES=$(grep "^include" settings.gradle | cut -d"'" -f2 | cut -d":" -f2 | tr '\n' ' ')
+ echo "Detected modules: $MODULES"
+ echo ""
+
echo "Checking compiled class files for each module:"
- for module in main events logger; do
+ for module in $MODULES; do
MODULE_CLASSES_DIR="${module}/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes"
if [ -d "$MODULE_CLASSES_DIR" ]; then
CLASS_COUNT=$(find "$MODULE_CLASSES_DIR" -name "*.class" | wc -l)
@@ -98,7 +108,7 @@ jobs:
echo ""
echo "Checking JaCoCo execution data for each module:"
- for module in main events logger; do
+ for module in $MODULES; do
EXEC_FILE="${module}/build/jacoco/testDebugUnitTest.exec"
if [ -f "$EXEC_FILE" ]; then
EXEC_SIZE=$(wc -c < "$EXEC_FILE")
@@ -127,7 +137,7 @@ jobs:
echo "=== Verification Complete ==="
- name: SonarCloud Scan
- uses: SonarSource/sonarqube-scan-action@v6
+ uses: SonarSource/sonarqube-scan-action@v7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
diff --git a/api/README.md b/api/README.md
index 0a58d3927..e455d981e 100644
--- a/api/README.md
+++ b/api/README.md
@@ -3,4 +3,3 @@
This module contains the public API interfaces and types exposed to consumers of the Split SDK.
Classes in this module are part of the public API contract and should maintain backwards compatibility.
-
diff --git a/api/src/main/java/io/split/android/client/SplitClient.java b/api/src/main/java/io/split/android/client/SplitClient.java
index 63d35f457..ba315e0e6 100644
--- a/api/src/main/java/io/split/android/client/SplitClient.java
+++ b/api/src/main/java/io/split/android/client/SplitClient.java
@@ -7,6 +7,7 @@
import java.util.Map;
import io.split.android.client.attributes.AttributesManager;
+import io.split.android.client.events.SdkEventListener;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
@@ -179,6 +180,33 @@ public interface SplitClient extends AttributesManager {
void on(SplitEvent event, SplitEventTask task);
+ /**
+ * Registers an event listener for SDK events that provide typed metadata.
+ *
+ * This method provides type-safe callbacks for SDK_UPDATE and SDK_READY_FROM_CACHE events.
+ * Override the methods you need in the listener.
+ *
+ * Example usage:
+ *
{@code
+ * client.addEventListener(new SdkEventListener() {
+ * @Override
+ * public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
+ * List flags = metadata.getUpdatedFlags();
+ * // Handle on background thread
+ * }
+ *
+ * @Override
+ * public void onReadyFromCacheView(SplitClient client, SdkReadyFromCacheMetadata metadata) {
+ * // Handle on main/UI thread
+ * Boolean freshInstall = metadata.isFreshInstall();
+ * }
+ * });
+ * }
+ *
+ * @param listener the event listener to register
+ */
+ void addEventListener(SdkEventListener listener);
+
/**
* Enqueue a new event to be sent to Split data collection services.
*
diff --git a/api/src/main/java/io/split/android/client/events/SdkEventListener.java b/api/src/main/java/io/split/android/client/events/SdkEventListener.java
new file mode 100644
index 000000000..d86946a16
--- /dev/null
+++ b/api/src/main/java/io/split/android/client/events/SdkEventListener.java
@@ -0,0 +1,84 @@
+package io.split.android.client.events;
+
+import io.split.android.client.SplitClient;
+
+/**
+ * Abstract class for handling SDK events with typed metadata.
+ *
+ * Extend this class and override the methods you need to handle specific SDK events.
+ * Each event has two callback options:
+ *
+ * Background thread callbacks (e.g., {@link #onUpdate}) - executed immediately on a background thread
+ * Main thread callbacks (e.g., {@link #onUpdateView}) - executed on the main/UI thread
+ *
+ *
+ * Example usage:
+ *
{@code
+ * client.addEventListener(new SdkEventListener() {
+ * @Override
+ * public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
+ * List flags = metadata.getUpdatedFlags();
+ * // Handle updated flags on background thread
+ * }
+ *
+ * @Override
+ * public void onReadyFromCacheView(SplitClient client, SdkReadyFromCacheMetadata metadata) {
+ * // Handle cache ready on main/UI thread
+ * Boolean freshInstall = metadata.isFreshInstall();
+ * }
+ * });
+ * }
+ */
+public abstract class SdkEventListener {
+
+ /**
+ * Called when SDK_UPDATE event occurs, executed on a background thread.
+ *
+ * Override this method to handle SDK_UPDATE events with typed metadata.
+ *
+ * @param client the Split client instance
+ * @param metadata the typed metadata containing updated flag information
+ */
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
+ // Default empty implementation
+ }
+
+ /**
+ * Called when SDK_READY_FROM_CACHE event occurs, executed on a background thread.
+ *
+ * Override this method to handle SDK_READY_FROM_CACHE events with typed metadata.
+ *
+ * @param client the Split client instance
+ * @param metadata the typed metadata containing cache information
+ */
+ public void onReadyFromCache(SplitClient client, SdkReadyFromCacheMetadata metadata) {
+ // Default empty implementation
+ }
+
+ /**
+ * Called when SDK_UPDATE event occurs, executed on the main/UI thread.
+ *
+ * Override this method to handle SDK_UPDATE events with typed metadata on the main thread.
+ * Use this when you need to update UI components.
+ *
+ * @param client the Split client instance
+ * @param metadata the typed metadata containing updated flag information
+ */
+ public void onUpdateView(SplitClient client, SdkUpdateMetadata metadata) {
+ // Default empty implementation
+ }
+
+ /**
+ * Called when SDK_READY_FROM_CACHE event occurs, executed on the main/UI thread.
+ *
+ * Override this method to handle SDK_READY_FROM_CACHE events with typed metadata on the main thread.
+ * Use this when you need to update UI components.
+ *
+ * @param client the Split client instance
+ * @param metadata the typed metadata containing cache information
+ */
+ public void onReadyFromCacheView(SplitClient client, SdkReadyFromCacheMetadata metadata) {
+ // Default empty implementation
+ }
+}
+
diff --git a/api/src/main/java/io/split/android/client/events/SdkReadyFromCacheMetadata.java b/api/src/main/java/io/split/android/client/events/SdkReadyFromCacheMetadata.java
new file mode 100644
index 000000000..3f1a883ed
--- /dev/null
+++ b/api/src/main/java/io/split/android/client/events/SdkReadyFromCacheMetadata.java
@@ -0,0 +1,49 @@
+package io.split.android.client.events;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Typed metadata for SDK_READY_FROM_CACHE events.
+ *
+ * Contains information about the cache state when the SDK is ready from cache.
+ */
+public final class SdkReadyFromCacheMetadata {
+
+ @Nullable
+ private final Boolean mFreshInstall;
+
+ @Nullable
+ private final Long mLastUpdateTimestamp;
+
+ /**
+ * Creates a new SdkReadyFromCacheMetadata instance.
+ *
+ * @param freshInstall true if this is a fresh install with no usable cache, or null if not available
+ * @param lastUpdateTimestamp the last successful cache timestamp in milliseconds since epoch, or null if not available
+ */
+ public SdkReadyFromCacheMetadata(@Nullable Boolean freshInstall, @Nullable Long lastUpdateTimestamp) {
+ mFreshInstall = freshInstall;
+ mLastUpdateTimestamp = lastUpdateTimestamp;
+ }
+
+ /**
+ * Returns whether this is a fresh install with no usable cache.
+ *
+ * @return true if fresh install, false otherwise, or null if not available
+ */
+ @Nullable
+ public Boolean isFreshInstall() {
+ return mFreshInstall;
+ }
+
+ /**
+ * Returns the last successful cache timestamp in milliseconds since epoch.
+ *
+ * @return the timestamp, or null if not available
+ */
+ @Nullable
+ public Long getLastUpdateTimestamp() {
+ return mLastUpdateTimestamp;
+ }
+}
+
diff --git a/api/src/main/java/io/split/android/client/events/SdkUpdateMetadata.java b/api/src/main/java/io/split/android/client/events/SdkUpdateMetadata.java
new file mode 100644
index 000000000..a9e6772b6
--- /dev/null
+++ b/api/src/main/java/io/split/android/client/events/SdkUpdateMetadata.java
@@ -0,0 +1,36 @@
+package io.split.android.client.events;
+
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+/**
+ * Typed metadata for SDK_UPDATE events.
+ *
+ * Contains information about flags that were updated in the event.
+ */
+public final class SdkUpdateMetadata {
+
+ @Nullable
+ private final List mUpdatedFlags;
+
+ /**
+ * Creates a new SdkUpdateMetadata instance.
+ *
+ * @param updatedFlags the list of flag names that were updated, or null if not available
+ */
+ public SdkUpdateMetadata(@Nullable List updatedFlags) {
+ mUpdatedFlags = updatedFlags;
+ }
+
+ /**
+ * Returns the list of flag names that changed in this update.
+ *
+ * @return the list of updated flag names, or null if not available
+ */
+ @Nullable
+ public List getUpdatedFlags() {
+ return mUpdatedFlags;
+ }
+}
+
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 f880e0fe1..c2b704cf5 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,16 +1,11 @@
package io.split.android.client.events;
-import androidx.annotation.Nullable;
-
import io.split.android.client.SplitClient;
-import io.split.android.client.api.EventMetadata;
/**
* 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:
*
@@ -18,26 +13,15 @@
* {@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
- *
+ * For events with metadata (like SDK_UPDATE or SDK_READY_FROM_CACHE), use
+ * {@link SdkEventListener} instead for type-safe metadata access.
*
* 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
- * }
- *
+ * client.on(SplitEvent.SDK_READY, new SplitEventTask() {
* @Override
* public void onPostExecution(SplitClient client) {
- * // Legacy handling (also called if both are implemented)
+ * // SDK is ready, start using Split
* }
* });
* }
@@ -46,7 +30,7 @@ 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.
+ * Override this method to handle events on a background thread.
* This method is executed immediately and is faster than {@link #onPostExecutionView(SplitClient)}.
*
* @param client the Split client instance
@@ -59,7 +43,7 @@ public void onPostExecution(SplitClient client) {
/**
* Called when an event occurs, executed on the main/UI thread.
*
- * Override this method to handle events on the main thread without metadata.
+ * Override this method to handle events on the main thread.
* Use this when you need to update UI components.
*
* Note: This method is queued on the main looper, so execution may be delayed
@@ -71,43 +55,4 @@ public void onPostExecution(SplitClient client) {
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/api/src/test/java/io/split/android/client/events/SdkReadyFromCacheMetadataTest.java b/api/src/test/java/io/split/android/client/events/SdkReadyFromCacheMetadataTest.java
new file mode 100644
index 000000000..64fd003f4
--- /dev/null
+++ b/api/src/test/java/io/split/android/client/events/SdkReadyFromCacheMetadataTest.java
@@ -0,0 +1,66 @@
+package io.split.android.client.events;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class SdkReadyFromCacheMetadataTest {
+
+ @Test
+ public void isFreshInstallReturnsNullWhenConstructedWithNull() {
+ SdkReadyFromCacheMetadata metadata = new SdkReadyFromCacheMetadata(null, null);
+
+ assertNull(metadata.isFreshInstall());
+ }
+
+ @Test
+ public void isFreshInstallReturnsTrueWhenConstructedWithTrue() {
+ SdkReadyFromCacheMetadata metadata = new SdkReadyFromCacheMetadata(true, null);
+
+ assertTrue(metadata.isFreshInstall());
+ }
+
+ @Test
+ public void isFreshInstallReturnsFalseWhenConstructedWithFalse() {
+ SdkReadyFromCacheMetadata metadata = new SdkReadyFromCacheMetadata(false, null);
+
+ assertFalse(metadata.isFreshInstall());
+ }
+
+ @Test
+ public void getLastUpdateTimestampReturnsNullWhenConstructedWithNull() {
+ SdkReadyFromCacheMetadata metadata = new SdkReadyFromCacheMetadata(null, null);
+
+ assertNull(metadata.getLastUpdateTimestamp());
+ }
+
+ @Test
+ public void getLastUpdateTimestampReturnsValueWhenConstructedWithValue() {
+ long timestamp = 1704067200000L;
+ SdkReadyFromCacheMetadata metadata = new SdkReadyFromCacheMetadata(null, timestamp);
+
+ assertEquals(Long.valueOf(timestamp), metadata.getLastUpdateTimestamp());
+ }
+
+ @Test
+ public void bothValuesReturnCorrectlyWhenBothAreSet() {
+ long timestamp = 1704067200000L;
+ SdkReadyFromCacheMetadata metadata = new SdkReadyFromCacheMetadata(true, timestamp);
+
+ assertTrue(metadata.isFreshInstall());
+ assertEquals(Long.valueOf(timestamp), metadata.getLastUpdateTimestamp());
+ }
+
+ @Test
+ public void bothValuesReturnCorrectlyWhenFreshInstallIsFalse() {
+ long timestamp = 1704067200000L;
+ SdkReadyFromCacheMetadata metadata = new SdkReadyFromCacheMetadata(false, timestamp);
+
+ assertFalse(metadata.isFreshInstall());
+ assertEquals(Long.valueOf(timestamp), metadata.getLastUpdateTimestamp());
+ }
+}
+
diff --git a/api/src/test/java/io/split/android/client/events/SdkUpdateMetadataTest.java b/api/src/test/java/io/split/android/client/events/SdkUpdateMetadataTest.java
new file mode 100644
index 000000000..143c2b259
--- /dev/null
+++ b/api/src/test/java/io/split/android/client/events/SdkUpdateMetadataTest.java
@@ -0,0 +1,46 @@
+package io.split.android.client.events;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class SdkUpdateMetadataTest {
+
+ @Test
+ public void getUpdatedFlagsReturnsNullWhenConstructedWithNull() {
+ SdkUpdateMetadata metadata = new SdkUpdateMetadata(null);
+
+ assertNull(metadata.getUpdatedFlags());
+ }
+
+ @Test
+ public void getUpdatedFlagsReturnsEmptyListWhenConstructedWithEmptyList() {
+ SdkUpdateMetadata metadata = new SdkUpdateMetadata(Collections.emptyList());
+
+ assertEquals(Collections.emptyList(), metadata.getUpdatedFlags());
+ }
+
+ @Test
+ public void getUpdatedFlagsReturnsListWhenConstructedWithList() {
+ List flags = Arrays.asList("flag1", "flag2", "flag3");
+ SdkUpdateMetadata metadata = new SdkUpdateMetadata(flags);
+
+ assertEquals(flags, metadata.getUpdatedFlags());
+ }
+
+ @Test
+ public void getUpdatedFlagsReturnsSingleItemList() {
+ List flags = Collections.singletonList("singleFlag");
+ SdkUpdateMetadata metadata = new SdkUpdateMetadata(flags);
+
+ assertEquals(flags, metadata.getUpdatedFlags());
+ assertEquals(1, metadata.getUpdatedFlags().size());
+ assertEquals("singleFlag", metadata.getUpdatedFlags().get(0));
+ }
+}
+
diff --git a/build.gradle b/build.gradle
index 110bd8d03..8ce6071dd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,8 +8,8 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:9.0.0-beta02'
- classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0'
+ classpath 'com.android.tools.build:gradle:9.0.0-rc02'
+ classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10'
classpath "com.vanniktech:gradle-maven-publish-plugin:0.34.0"
}
}
@@ -19,7 +19,7 @@ apply plugin: 'com.vanniktech.maven.publish'
apply from: "$rootDir/gradle/jacoco-root.gradle"
ext {
- splitVersion = '5.5.0-rc1'
+ splitVersion = '5.5.0-rc5'
jacocoVersion = '0.8.8'
}
diff --git a/events-domain/src/main/java/io/split/android/client/events/EventsManagerCoordinator.java b/events-domain/src/main/java/io/split/android/client/events/EventsManagerCoordinator.java
index 553b1a094..4fba4b4d4 100644
--- a/events-domain/src/main/java/io/split/android/client/events/EventsManagerCoordinator.java
+++ b/events-domain/src/main/java/io/split/android/client/events/EventsManagerCoordinator.java
@@ -10,7 +10,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.api.Key;
/**
@@ -104,13 +104,19 @@ public void registerEventsManager(Key key, ISplitEventsManager splitEventsManage
/**
* Unregisters the events manager for a client key.
+ *
+ * If the removed manager is a {@link SplitEventsManager}, its {@code destroy()} method
+ * will be called to clean up resources.
*
* @param key the client key to unregister
*/
@Override
public void unregisterEventsManager(Key key) {
if (key != null) {
- mManagers.remove(key);
+ ISplitEventsManager removed = mManagers.remove(key);
+ if (removed instanceof SplitEventsManager) {
+ ((SplitEventsManager) removed).destroy();
+ }
}
}
diff --git a/events-domain/src/main/java/io/split/android/client/events/ISplitEventsManager.java b/events-domain/src/main/java/io/split/android/client/events/ISplitEventsManager.java
index 32580a40d..d6dd48859 100644
--- a/events-domain/src/main/java/io/split/android/client/events/ISplitEventsManager.java
+++ b/events-domain/src/main/java/io/split/android/client/events/ISplitEventsManager.java
@@ -2,7 +2,7 @@
import androidx.annotation.Nullable;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
public interface ISplitEventsManager {
diff --git a/events-domain/src/main/java/io/split/android/client/events/ListenableEventsManager.java b/events-domain/src/main/java/io/split/android/client/events/ListenableEventsManager.java
index f0b4aff46..a8ad9c0f1 100644
--- a/events-domain/src/main/java/io/split/android/client/events/ListenableEventsManager.java
+++ b/events-domain/src/main/java/io/split/android/client/events/ListenableEventsManager.java
@@ -8,5 +8,7 @@ public interface ListenableEventsManager {
void register(SplitEvent event, SplitEventTask task);
+ void registerEventListener(SdkEventListener listener);
+
boolean eventAlreadyTriggered(SplitEvent event);
}
diff --git a/events-domain/src/main/java/io/split/android/client/events/SplitEventDelivery.java b/events-domain/src/main/java/io/split/android/client/events/SplitEventDelivery.java
index bcc13a50d..5930fd21c 100644
--- a/events-domain/src/main/java/io/split/android/client/events/SplitEventDelivery.java
+++ b/events-domain/src/main/java/io/split/android/client/events/SplitEventDelivery.java
@@ -5,7 +5,7 @@
import io.harness.events.EventDelivery;
import io.harness.events.EventHandler;
import io.harness.events.Logging;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
/**
* Event delivery implementation for Split SDK events.
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 cc417efe2..5cf91b3e0 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
@@ -10,7 +10,9 @@
import io.harness.events.EventsManager;
import io.harness.events.EventsManagers;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.SplitClient;
+import io.split.android.client.events.metadata.EventMetadata;
+import io.split.android.client.events.metadata.TypedTaskConverter;
import io.split.android.client.events.executors.SplitEventExecutorResources;
import io.split.android.client.events.executors.SplitEventExecutorResourcesImpl;
import io.split.android.client.service.executor.SplitTaskExecutionInfo;
@@ -110,6 +112,27 @@ public void register(SplitEvent event, SplitEventTask task) {
);
}
+ @Override
+ public void registerEventListener(SdkEventListener listener) {
+ requireNonNull(listener);
+
+ // Register SDK_UPDATE handlers (bg + main)
+ mDualExecutorRegistration.register(
+ mEventsManager,
+ SplitEvent.SDK_UPDATE,
+ createUpdateBackgroundHandler(listener),
+ createUpdateMainThreadHandler(listener)
+ );
+
+ // Register SDK_READY_FROM_CACHE handlers (bg + main)
+ mDualExecutorRegistration.register(
+ mEventsManager,
+ SplitEvent.SDK_READY_FROM_CACHE,
+ createReadyFromCacheBackgroundHandler(listener),
+ createReadyFromCacheMainThreadHandler(listener)
+ );
+ }
+
@Override
public boolean eventAlreadyTriggered(SplitEvent event) {
return mEventsManager.eventAlreadyTriggered(event);
@@ -145,71 +168,71 @@ public void run() {
}
private EventHandler createBackgroundHandler(final SplitEventTask task) {
- return createEventHandler(task, "background", new TaskMethodCaller() {
- @Override
- public void callWithMetadata(EventMetadata metadata) {
- task.onPostExecution(mResources.getSplitClient(), metadata);
- }
-
- @Override
- public void callWithoutMetadata() {
- task.onPostExecution(mResources.getSplitClient());
- }
- });
+ return (event, metadata) -> {
+ SplitClient client = mResources.getSplitClient();
+ executeBackgroundTask(task, client, metadata);
+ };
}
private EventHandler createMainThreadHandler(final SplitEventTask task) {
- return createEventHandler(task, "main thread", new TaskMethodCaller() {
- @Override
- public void callWithMetadata(EventMetadata metadata) {
- task.onPostExecutionView(mResources.getSplitClient(), metadata);
- }
+ return (event, metadata) -> {
+ SplitClient client = mResources.getSplitClient();
+ executeMainThreadTask(task, client, metadata);
+ };
+ }
- @Override
- public void callWithoutMetadata() {
- task.onPostExecutionView(mResources.getSplitClient());
- }
- });
+ // SdkEventListener handlers for SDK_UPDATE
+ private EventHandler createUpdateBackgroundHandler(final SdkEventListener listener) {
+ return (event, metadata) -> {
+ SplitClient client = mResources.getSplitClient();
+ SdkUpdateMetadata typedMetadata = TypedTaskConverter.convertForSdkUpdate(metadata);
+ executeMethod(() -> listener.onUpdate(client, typedMetadata));
+ };
}
- /**
- * Helper interface for calling task methods.
- */
- private interface TaskMethodCaller {
- void callWithMetadata(EventMetadata metadata) throws Exception;
- void callWithoutMetadata() throws Exception;
+ private EventHandler createUpdateMainThreadHandler(final SdkEventListener listener) {
+ return (event, metadata) -> {
+ SplitClient client = mResources.getSplitClient();
+ SdkUpdateMetadata typedMetadata = TypedTaskConverter.convertForSdkUpdate(metadata);
+ executeMethod(() -> listener.onUpdateView(client, typedMetadata));
+ };
}
- 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);
- }
+ // SdkEventListener handlers for SDK_READY_FROM_CACHE
+ private EventHandler createReadyFromCacheBackgroundHandler(final SdkEventListener listener) {
+ return (event, metadata) -> {
+ SplitClient client = mResources.getSplitClient();
+ SdkReadyFromCacheMetadata typedMetadata = TypedTaskConverter.convertForSdkReadyFromCache(metadata);
+ executeMethod(() -> listener.onReadyFromCache(client, typedMetadata));
+ };
+ }
- private void executeTaskMethod(EventMetadata metadata, boolean withMetadata, String threadType, TaskMethodCaller caller) {
- try {
- if (withMetadata) {
- caller.callWithMetadata(metadata);
- } else {
- caller.callWithoutMetadata();
- }
- } catch (SplitEventTaskMethodNotImplementedException e) {
- // Method not implemented by client, ignore
- } catch (Exception e) {
- String errorPrefix = withMetadata
- ? "Error executing " + threadType + " event task (with metadata): "
- : "Error executing " + threadType + " event task: ";
- Logger.e(errorPrefix + e.getMessage());
- }
- }
+ private EventHandler createReadyFromCacheMainThreadHandler(final SdkEventListener listener) {
+ return (event, metadata) -> {
+ SplitClient client = mResources.getSplitClient();
+ SdkReadyFromCacheMetadata typedMetadata = TypedTaskConverter.convertForSdkReadyFromCache(metadata);
+ executeMethod(() -> listener.onReadyFromCacheView(client, typedMetadata));
};
}
+ private void executeBackgroundTask(SplitEventTask task, SplitClient client, EventMetadata metadata) {
+ executeMethod(() -> task.onPostExecution(client));
+ }
+
+ private void executeMainThreadTask(SplitEventTask task, SplitClient client, EventMetadata metadata) {
+ executeMethod(() -> task.onPostExecutionView(client));
+ }
+
+ private void executeMethod(Runnable method) {
+ try {
+ method.run();
+ } catch (SplitEventTaskMethodNotImplementedException e) {
+ // Method not implemented by client, ignore
+ } catch (Exception e) {
+ Logger.e("Error executing event task: " + e.getMessage());
+ }
+ }
+
private Executor createBackgroundExecutor(final SplitTaskExecutor taskExecutor) {
return command -> taskExecutor.submit(() -> {
try {
diff --git a/api/src/main/java/io/split/android/client/api/EventMetadata.java b/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadata.java
similarity index 67%
rename from api/src/main/java/io/split/android/client/api/EventMetadata.java
rename to events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadata.java
index e1648c388..1d531b131 100644
--- a/api/src/main/java/io/split/android/client/api/EventMetadata.java
+++ b/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadata.java
@@ -1,31 +1,35 @@
-package io.split.android.client.api;
+package io.split.android.client.events.metadata;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Collection;
-import java.util.Map;
-import java.util.Set;
/**
* Represents metadata associated with SDK events.
*
+ * This is an internal API for SDK infrastructure use.
+ * Consumers should use the typed metadata classes instead:
+ * {@code SdkUpdateMetadata} and {@code SdkReadyFromCacheMetadata}.
+ *
* Values are sanitized to only allow String, Number, Boolean, or List<String>.
*/
public interface EventMetadata {
/**
- * Returns the set of keys in this metadata.
- *
- * @return set of keys
+ * Returns the number of entries in this metadata.
*/
- @NonNull
- Set keys();
+ int size();
+
+ /**
+ * Returns whether this metadata has no entries.
+ */
+ default boolean isEmpty() {
+ return size() == 0;
+ }
/**
* Returns the collection of values in this metadata.
- *
- * @return collection of values
*/
@NonNull
Collection values();
@@ -46,13 +50,4 @@ public interface EventMetadata {
* @return true if the key exists, false otherwise
*/
boolean containsKey(@NonNull String key);
-
- /**
- * Returns a copy of the underlying data as a Map.
- *
- * @return a copy of the metadata map
- */
- @NonNull
- Map toMap();
}
-
diff --git a/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataBuilder.java b/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataBuilder.java
index 76cb30289..86c3b142b 100644
--- a/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataBuilder.java
+++ b/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataBuilder.java
@@ -7,8 +7,6 @@
import java.util.List;
import java.util.Map;
-import io.split.android.client.api.EventMetadata;
-
/**
* Builder for creating {@link EventMetadata} instances.
*
diff --git a/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataHelpers.java b/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataHelpers.java
index d7fc334a7..67dda836e 100644
--- a/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataHelpers.java
+++ b/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataHelpers.java
@@ -6,8 +6,6 @@
import java.util.HashSet;
import java.util.List;
-import io.split.android.client.api.EventMetadata;
-
/**
* Helper class for creating {@link EventMetadata} instances.
*
@@ -15,17 +13,13 @@
*/
public class EventMetadataHelpers {
- private static final String KEY_UPDATED_FLAGS = "updatedFlags";
- private static final String KEY_LAST_UPDATE_TIMESTAMP = "lastUpdateTimestamp";
- private static final String KEY_FRESH_INSTALL = "freshInstall";
-
private EventMetadataHelpers() {
// Utility class
}
public static EventMetadata createUpdatedFlagsMetadata(List updatedSplitNames) {
return new EventMetadataBuilder()
- .put(KEY_UPDATED_FLAGS, new ArrayList<>(new HashSet<>(updatedSplitNames)))
+ .put(MetadataKeys.UPDATED_FLAGS, new ArrayList<>(new HashSet<>(updatedSplitNames)))
.build();
}
@@ -38,10 +32,10 @@ public static EventMetadata createUpdatedFlagsMetadata(List updatedSplit
*/
public static EventMetadata createCacheReadyMetadata(@Nullable Long lastUpdateTimestamp, boolean freshInstall) {
EventMetadataBuilder builder = new EventMetadataBuilder()
- .put(KEY_FRESH_INSTALL, freshInstall);
+ .put(MetadataKeys.FRESH_INSTALL, freshInstall);
if (lastUpdateTimestamp != null) {
- builder.put(KEY_LAST_UPDATE_TIMESTAMP, lastUpdateTimestamp);
+ builder.put(MetadataKeys.LAST_UPDATE_TIMESTAMP, lastUpdateTimestamp);
}
return builder.build();
diff --git a/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataImpl.java b/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataImpl.java
index 8c9b73ffa..97aace947 100644
--- a/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataImpl.java
+++ b/events-domain/src/main/java/io/split/android/client/events/metadata/EventMetadataImpl.java
@@ -9,9 +9,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-
-import io.split.android.client.api.EventMetadata;
/**
* Implementation of {@link EventMetadata}.
@@ -34,10 +31,9 @@ class EventMetadataImpl implements EventMetadata {
mData = Collections.unmodifiableMap(copy);
}
- @NonNull
@Override
- public Set keys() {
- return mData.keySet();
+ public int size() {
+ return mData.size();
}
@NonNull
@@ -56,19 +52,4 @@ public Object get(@NonNull String key) {
public boolean containsKey(@NonNull String key) {
return mData.containsKey(key);
}
-
- @NonNull
- @Override
- public Map toMap() {
- Map copy = new HashMap<>();
- for (Map.Entry entry : mData.entrySet()) {
- Object value = entry.getValue();
- if (value instanceof List) {
- copy.put(entry.getKey(), new ArrayList<>((List>) value));
- } else {
- copy.put(entry.getKey(), value);
- }
- }
- return copy;
- }
}
diff --git a/events-domain/src/main/java/io/split/android/client/events/metadata/MetadataKeys.java b/events-domain/src/main/java/io/split/android/client/events/metadata/MetadataKeys.java
new file mode 100644
index 000000000..c76dd578f
--- /dev/null
+++ b/events-domain/src/main/java/io/split/android/client/events/metadata/MetadataKeys.java
@@ -0,0 +1,34 @@
+package io.split.android.client.events.metadata;
+
+/**
+ * Consolidated metadata keys for SDK events.
+ *
+ * Package-private - for internal SDK use only.
+ */
+final class MetadataKeys {
+
+ private MetadataKeys() {
+ // no instances
+ }
+
+ // SDK_UPDATE event keys
+
+ /**
+ * Names of flags that changed in this update.
+ */
+ static final String UPDATED_FLAGS = "updatedFlags";
+
+ // SDK_READY_FROM_CACHE event keys
+
+ /**
+ * True if this is a fresh install with no usable cache.
+ */
+ static final String FRESH_INSTALL = "freshInstall";
+
+ /**
+ * Last successful cache timestamp in milliseconds since epoch.
+ *
+ * May be absent when not available.
+ */
+ static final String LAST_UPDATE_TIMESTAMP = "lastUpdateTimestamp";
+}
diff --git a/events-domain/src/main/java/io/split/android/client/events/metadata/TypedTaskConverter.java b/events-domain/src/main/java/io/split/android/client/events/metadata/TypedTaskConverter.java
new file mode 100644
index 000000000..a93123ef6
--- /dev/null
+++ b/events-domain/src/main/java/io/split/android/client/events/metadata/TypedTaskConverter.java
@@ -0,0 +1,52 @@
+package io.split.android.client.events.metadata;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.List;
+
+import io.split.android.client.events.SdkReadyFromCacheMetadata;
+import io.split.android.client.events.SdkUpdateMetadata;
+
+/**
+ * Converts {@link EventMetadata} to typed metadata objects for typed event tasks.
+*/
+public class TypedTaskConverter {
+
+ private TypedTaskConverter() {
+ // Utility class
+ }
+
+ /**
+ * Converts EventMetadata to SdkUpdateMetadata.
+ *
+ * @param metadata the event metadata, may be null
+ * @return the typed metadata for SDK_UPDATE events
+ */
+ @NonNull
+ @SuppressWarnings("unchecked")
+ public static SdkUpdateMetadata convertForSdkUpdate(@Nullable EventMetadata metadata) {
+ List updatedFlags = null;
+ if (metadata != null) {
+ updatedFlags = (List) metadata.get(MetadataKeys.UPDATED_FLAGS);
+ }
+ return new SdkUpdateMetadata(updatedFlags);
+ }
+
+ /**
+ * Converts EventMetadata to SdkReadyFromCacheMetadata.
+ *
+ * @param metadata the event metadata, may be null
+ * @return the typed metadata for SDK_READY_FROM_CACHE events
+ */
+ @NonNull
+ public static SdkReadyFromCacheMetadata convertForSdkReadyFromCache(@Nullable EventMetadata metadata) {
+ Boolean freshInstall = null;
+ Long lastUpdateTimestamp = null;
+ if (metadata != null) {
+ freshInstall = (Boolean) metadata.get(MetadataKeys.FRESH_INSTALL);
+ lastUpdateTimestamp = (Long) metadata.get(MetadataKeys.LAST_UPDATE_TIMESTAMP);
+ }
+ return new SdkReadyFromCacheMetadata(freshInstall, lastUpdateTimestamp);
+ }
+}
diff --git a/events-domain/src/test/java/io/split/android/client/events/TypedTaskConversionTest.java b/events-domain/src/test/java/io/split/android/client/events/TypedTaskConversionTest.java
new file mode 100644
index 000000000..f1f93f347
--- /dev/null
+++ b/events-domain/src/test/java/io/split/android/client/events/TypedTaskConversionTest.java
@@ -0,0 +1,67 @@
+package io.split.android.client.events;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import io.split.android.client.events.metadata.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadataHelpers;
+import io.split.android.client.events.metadata.TypedTaskConverter;
+
+/**
+ * Tests for typed task metadata conversion.
+ */
+public class TypedTaskConversionTest {
+
+ @Test
+ public void convertForSdkUpdateConvertsMetadataCorrectly() {
+ List expectedFlags = Arrays.asList("flag1", "flag2");
+
+ EventMetadata eventMetadata = EventMetadataHelpers.createUpdatedFlagsMetadata(expectedFlags);
+
+ // Call conversion method
+ SdkUpdateMetadata converted = TypedTaskConverter.convertForSdkUpdate(eventMetadata);
+
+ assertNotNull(converted);
+ assertEquals(expectedFlags.size(), converted.getUpdatedFlags().size());
+ assertTrue(converted.getUpdatedFlags().containsAll(expectedFlags));
+ }
+
+ @Test
+ public void convertForSdkReadyFromCacheConvertsMetadataCorrectly() {
+ long expectedTimestamp = 1704067200000L;
+
+ EventMetadata eventMetadata = EventMetadataHelpers.createCacheReadyMetadata(expectedTimestamp, true);
+
+ // Call conversion method
+ SdkReadyFromCacheMetadata converted = TypedTaskConverter.convertForSdkReadyFromCache(eventMetadata);
+
+ assertNotNull(converted);
+ assertTrue(converted.isFreshInstall());
+ assertEquals(Long.valueOf(expectedTimestamp), converted.getLastUpdateTimestamp());
+ }
+
+ @Test
+ public void convertForSdkUpdateHandlesNullMetadata() {
+ SdkUpdateMetadata converted = TypedTaskConverter.convertForSdkUpdate(null);
+
+ assertNotNull(converted);
+ assertNull(converted.getUpdatedFlags());
+ }
+
+ @Test
+ public void convertForSdkReadyFromCacheHandlesNullMetadata() {
+ SdkReadyFromCacheMetadata converted = TypedTaskConverter.convertForSdkReadyFromCache(null);
+
+ assertNotNull(converted);
+ assertNull(converted.isFreshInstall());
+ assertNull(converted.getLastUpdateTimestamp());
+ }
+}
+
diff --git a/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataBuilderTest.java b/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataBuilderTest.java
index 4652c1ba5..8f561e0c2 100644
--- a/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataBuilderTest.java
+++ b/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataBuilderTest.java
@@ -15,8 +15,6 @@
import java.util.Arrays;
import java.util.List;
-import io.split.android.client.api.EventMetadata;
-
public class EventMetadataBuilderTest {
@Mock
@@ -94,7 +92,7 @@ public void putIncludesValueWhenValidatorReturnsTrue() {
public void buildCreatesEmptyMetadataWhenNothingAdded() {
EventMetadata metadata = new EventMetadataBuilder().build();
- assertTrue(metadata.keys().isEmpty());
+ assertTrue(metadata.isEmpty());
}
@Test
@@ -112,7 +110,7 @@ public void putIntegerAddsValue() {
.put("count", 42)
.build();
- assertEquals(42, metadata.get("count"));
+ assertEquals(Integer.valueOf(42), metadata.get("count"));
}
@Test
@@ -121,7 +119,7 @@ public void putLongAddsValue() {
.put("timestamp", 1234567890L)
.build();
- assertEquals(1234567890L, metadata.get("timestamp"));
+ assertEquals(Long.valueOf(1234567890L), metadata.get("timestamp"));
}
@Test
@@ -130,7 +128,7 @@ public void putDoubleAddsValue() {
.put("rate", 3.14)
.build();
- assertEquals(3.14, metadata.get("rate"));
+ assertEquals(Double.valueOf(3.14), metadata.get("rate"));
}
@Test
@@ -139,7 +137,7 @@ public void putBooleanTrueAddsValue() {
.put("enabled", true)
.build();
- assertEquals(true, metadata.get("enabled"));
+ assertEquals(Boolean.TRUE, metadata.get("enabled"));
}
@Test
@@ -148,7 +146,7 @@ public void putBooleanFalseAddsValue() {
.put("disabled", false)
.build();
- assertEquals(false, metadata.get("disabled"));
+ assertEquals(Boolean.FALSE, metadata.get("disabled"));
}
@Test
@@ -159,7 +157,7 @@ public void putListOfStringsAddsValue() {
.put("updatedFlags", flags)
.build();
- assertEquals(flags, metadata.get("updatedFlags"));
+ assertEquals(flags, metadata.get(MetadataKeys.UPDATED_FLAGS));
}
@Test
@@ -171,10 +169,10 @@ public void chainingMultiplePutsWorks() {
.put("list", Arrays.asList("a", "b"))
.build();
- assertEquals(4, metadata.keys().size());
+ assertEquals(4, metadata.size());
assertEquals("text", metadata.get("string"));
- assertEquals(100, metadata.get("number"));
- assertEquals(true, metadata.get("flag"));
+ assertEquals(Integer.valueOf(100), metadata.get("number"));
+ assertEquals(Boolean.TRUE, metadata.get("flag"));
assertEquals(Arrays.asList("a", "b"), metadata.get("list"));
}
diff --git a/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataHelpersTest.java b/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataHelpersTest.java
index d21bc8d3f..7fe8d577d 100644
--- a/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataHelpersTest.java
+++ b/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataHelpersTest.java
@@ -1,7 +1,6 @@
package io.split.android.client.events.metadata;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -10,19 +9,17 @@
import java.util.Arrays;
import java.util.List;
-import io.split.android.client.api.EventMetadata;
-
public class EventMetadataHelpersTest {
// Tests for createUpdatedFlagsMetadata (existing)
@Test
+ @SuppressWarnings("unchecked")
public void createUpdatedFlagsMetadataContainsFlags() {
List flags = Arrays.asList("flag1", "flag2", "flag3");
EventMetadata metadata = EventMetadataHelpers.createUpdatedFlagsMetadata(flags);
- assertTrue(metadata.containsKey("updatedFlags"));
- @SuppressWarnings("unchecked")
- List result = (List) metadata.get("updatedFlags");
+ assertTrue(metadata.containsKey(MetadataKeys.UPDATED_FLAGS));
+ List result = (List) metadata.get(MetadataKeys.UPDATED_FLAGS);
assertEquals(3, result.size());
assertTrue(result.contains("flag1"));
assertTrue(result.contains("flag2"));
@@ -34,33 +31,33 @@ public void createUpdatedFlagsMetadataContainsFlags() {
public void createCacheReadyMetadataWithTimestampAndFreshInstallFalse() {
EventMetadata metadata = EventMetadataHelpers.createCacheReadyMetadata(1234567890L, false);
- assertEquals(1234567890L, metadata.get("lastUpdateTimestamp"));
- assertEquals(false, metadata.get("freshInstall"));
+ assertEquals(Long.valueOf(1234567890L), metadata.get(MetadataKeys.LAST_UPDATE_TIMESTAMP));
+ assertEquals(Boolean.FALSE, metadata.get(MetadataKeys.FRESH_INSTALL));
}
@Test
public void createCacheReadyMetadataWithNullTimestampAndFreshInstallTrue() {
EventMetadata metadata = EventMetadataHelpers.createCacheReadyMetadata(null, true);
- assertNull(metadata.get("lastUpdateTimestamp"));
- assertEquals(true, metadata.get("freshInstall"));
+ assertNull(metadata.get(MetadataKeys.LAST_UPDATE_TIMESTAMP));
+ assertEquals(Boolean.TRUE, metadata.get(MetadataKeys.FRESH_INSTALL));
}
@Test
public void createCacheReadyMetadataKeysAreCorrect() {
EventMetadata metadata = EventMetadataHelpers.createCacheReadyMetadata(123L, false);
- assertTrue(metadata.containsKey("lastUpdateTimestamp"));
- assertTrue(metadata.containsKey("freshInstall"));
- assertEquals(2, metadata.keys().size());
+ assertTrue(metadata.containsKey(MetadataKeys.LAST_UPDATE_TIMESTAMP));
+ assertTrue(metadata.containsKey(MetadataKeys.FRESH_INSTALL));
+ assertEquals(2, metadata.size());
}
@Test
public void createCacheReadyMetadataWithZeroTimestamp() {
EventMetadata metadata = EventMetadataHelpers.createCacheReadyMetadata(0L, false);
- assertEquals(0L, metadata.get("lastUpdateTimestamp"));
- assertEquals(false, metadata.get("freshInstall"));
+ assertEquals(Long.valueOf(0L), metadata.get(MetadataKeys.LAST_UPDATE_TIMESTAMP));
+ assertEquals(Boolean.FALSE, metadata.get(MetadataKeys.FRESH_INSTALL));
}
@Test
@@ -69,8 +66,8 @@ public void createCacheReadyMetadataForCachePath() {
long storedTimestamp = 1700000000000L;
EventMetadata metadata = EventMetadataHelpers.createCacheReadyMetadata(storedTimestamp, false);
- assertFalse((Boolean) metadata.get("freshInstall"));
- assertEquals(storedTimestamp, metadata.get("lastUpdateTimestamp"));
+ assertEquals(Boolean.FALSE, metadata.get(MetadataKeys.FRESH_INSTALL));
+ assertEquals(Long.valueOf(storedTimestamp), metadata.get(MetadataKeys.LAST_UPDATE_TIMESTAMP));
}
@Test
@@ -78,8 +75,7 @@ public void createCacheReadyMetadataForSyncPath() {
// Sync path: freshInstall=true, timestamp=null
EventMetadata metadata = EventMetadataHelpers.createCacheReadyMetadata(null, true);
- assertTrue((Boolean) metadata.get("freshInstall"));
- assertNull(metadata.get("lastUpdateTimestamp"));
+ assertEquals(Boolean.TRUE, metadata.get(MetadataKeys.FRESH_INSTALL));
+ assertNull(metadata.get(MetadataKeys.LAST_UPDATE_TIMESTAMP));
}
}
-
diff --git a/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataImplTest.java b/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataImplTest.java
index 54059494e..5f539c6a6 100644
--- a/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataImplTest.java
+++ b/events-domain/src/test/java/io/split/android/client/events/metadata/EventMetadataImplTest.java
@@ -13,31 +13,30 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+
public class EventMetadataImplTest {
@Test
- public void keysReturnsAllKeys() {
+ public void sizeAndContainsKeyReflectStoredEntries() {
Map data = new HashMap<>();
data.put("key1", "value1");
data.put("key2", 42);
data.put("key3", true);
EventMetadataImpl metadata = new EventMetadataImpl(data);
- Set keys = metadata.keys();
- assertEquals(3, keys.size());
- assertTrue(keys.contains("key1"));
- assertTrue(keys.contains("key2"));
- assertTrue(keys.contains("key3"));
+ assertEquals(3, metadata.size());
+ assertTrue(metadata.containsKey("key1"));
+ assertTrue(metadata.containsKey("key2"));
+ assertTrue(metadata.containsKey("key3"));
}
@Test
- public void keysReturnsEmptySetForEmptyMetadata() {
+ public void isEmptyReturnsTrueForEmptyMetadata() {
EventMetadataImpl metadata = new EventMetadataImpl(new HashMap<>());
- assertTrue(metadata.keys().isEmpty());
+ assertTrue(metadata.isEmpty());
}
@Test
@@ -101,71 +100,6 @@ public void containsKeyReturnsFalseForNonExistingKey() {
assertFalse(metadata.containsKey("nonExistingKey"));
}
- @Test
- public void toMapReturnsACopyOfTheData() {
- Map data = new HashMap<>();
- data.put("key", "value");
-
- EventMetadataImpl metadata = new EventMetadataImpl(data);
- Map copy = metadata.toMap();
-
- assertEquals(1, copy.size());
- assertEquals("value", copy.get("key"));
-
- // Verify it's a copy by modifying it
- copy.put("newKey", "newValue");
- assertFalse(metadata.containsKey("newKey"));
- }
-
- @Test
- public void toMapReturnsEmptyMapForEmptyMetadata() {
- EventMetadataImpl metadata = new EventMetadataImpl(new HashMap<>());
-
- assertTrue(metadata.toMap().isEmpty());
- }
-
- @Test
- public void toMapReturnsModifiableCopyOfLists() {
- Map data = new HashMap<>();
- data.put("flags", Arrays.asList("flag_1", "flag_2"));
-
- EventMetadataImpl metadata = new EventMetadataImpl(data);
- Map copy = metadata.toMap();
-
- // Should be able to modify the list in the copy
- @SuppressWarnings("unchecked")
- List listInCopy = (List) copy.get("flags");
- listInCopy.add("flag_3");
-
- // Original metadata should not be affected
- @SuppressWarnings("unchecked")
- List originalList = (List) metadata.get("flags");
- assertEquals(2, originalList.size());
- assertEquals(Arrays.asList("flag_1", "flag_2"), originalList);
- }
-
- @Test
- public void toMapListsAreIndependentAcrossCalls() {
- Map data = new HashMap<>();
- data.put("flags", Arrays.asList("flag_1", "flag_2"));
-
- EventMetadataImpl metadata = new EventMetadataImpl(data);
-
- Map copy1 = metadata.toMap();
- Map copy2 = metadata.toMap();
-
- // Modify copy1's list
- @SuppressWarnings("unchecked")
- List list1 = (List) copy1.get("flags");
- list1.add("flag_3");
-
- // copy2's list should not be affected
- @SuppressWarnings("unchecked")
- List list2 = (List) copy2.get("flags");
- assertEquals(2, list2.size());
- assertEquals(Arrays.asList("flag_1", "flag_2"), list2);
- }
-
@Test
public void metadataIsImmutableAfterConstruction() {
Map data = new HashMap<>();
@@ -178,10 +112,11 @@ public void metadataIsImmutableAfterConstruction() {
// Metadata should not be affected
assertFalse(metadata.containsKey("newKey"));
- assertEquals(1, metadata.keys().size());
+ assertEquals(1, metadata.size());
}
@Test
+ @SuppressWarnings("unchecked")
public void listIsDefensivelyCopiedDuringConstruction() {
List originalList = new ArrayList<>(Arrays.asList("flag_1", "flag_2"));
Map data = new HashMap<>();
@@ -193,20 +128,19 @@ public void listIsDefensivelyCopiedDuringConstruction() {
originalList.add("flag_3");
// Metadata should not be affected
- @SuppressWarnings("unchecked")
List storedList = (List) metadata.get("flags");
assertEquals(2, storedList.size());
assertEquals(Arrays.asList("flag_1", "flag_2"), storedList);
}
@Test(expected = UnsupportedOperationException.class)
+ @SuppressWarnings("unchecked")
public void listReturnedByGetIsUnmodifiable() {
Map data = new HashMap<>();
data.put("flags", Arrays.asList("flag_1", "flag_2"));
EventMetadataImpl metadata = new EventMetadataImpl(data);
- @SuppressWarnings("unchecked")
List list = (List) metadata.get("flags");
// This should throw UnsupportedOperationException
diff --git a/events-domain/src/test/java/io/split/android/client/events/metadata/MetadataKeysTest.java b/events-domain/src/test/java/io/split/android/client/events/metadata/MetadataKeysTest.java
new file mode 100644
index 000000000..530dfd6a2
--- /dev/null
+++ b/events-domain/src/test/java/io/split/android/client/events/metadata/MetadataKeysTest.java
@@ -0,0 +1,27 @@
+package io.split.android.client.events.metadata;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link MetadataKeys}.
+ * Verifies that all metadata keys are correctly defined.
+ */
+public class MetadataKeysTest {
+
+ @Test
+ public void updatedFlagsKeyHasCorrectValue() {
+ assertEquals("updatedFlags", MetadataKeys.UPDATED_FLAGS);
+ }
+
+ @Test
+ public void freshInstallKeyHasCorrectValue() {
+ assertEquals("freshInstall", MetadataKeys.FRESH_INSTALL);
+ }
+
+ @Test
+ public void lastUpdateTimestampKeyHasCorrectValue() {
+ assertEquals("lastUpdateTimestamp", MetadataKeys.LAST_UPDATE_TIMESTAMP);
+ }
+}
diff --git a/main/src/androidTest/java/fake/SplitClientStub.java b/main/src/androidTest/java/fake/SplitClientStub.java
index 4acebddbc..b9d354bf9 100644
--- a/main/src/androidTest/java/fake/SplitClientStub.java
+++ b/main/src/androidTest/java/fake/SplitClientStub.java
@@ -11,6 +11,7 @@
import io.split.android.client.EvaluationOptions;
import io.split.android.client.SplitClient;
import io.split.android.client.SplitResult;
+import io.split.android.client.events.SdkEventListener;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
@@ -120,6 +121,11 @@ public void on(SplitEvent event, SplitEventTask task) {
}
+ @Override
+ public void addEventListener(SdkEventListener listener) {
+ // Stub implementation - does nothing
+ }
+
@Override
public boolean track(String eventType) {
return false;
diff --git a/main/src/androidTest/java/tests/integration/events/SdkEventsIntegrationTest.java b/main/src/androidTest/java/tests/integration/events/SdkEventsIntegrationTest.java
index 9cef41e98..515836a1f 100644
--- a/main/src/androidTest/java/tests/integration/events/SdkEventsIntegrationTest.java
+++ b/main/src/androidTest/java/tests/integration/events/SdkEventsIntegrationTest.java
@@ -35,8 +35,10 @@
import io.split.android.client.SplitClient;
import io.split.android.client.SplitClientConfig;
import io.split.android.client.SplitFactory;
-import io.split.android.client.api.EventMetadata;
import io.split.android.client.api.Key;
+import io.split.android.client.events.SdkEventListener;
+import io.split.android.client.events.SdkReadyFromCacheMetadata;
+import io.split.android.client.events.SdkUpdateMetadata;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.client.network.HttpMethod;
@@ -144,7 +146,7 @@ public void sdkReadyFromCacheFiresWhenCacheLoadingCompletes() throws Exception {
// And: a handler H is registered for sdkReadyFromCache
AtomicInteger handlerInvocationCount = new AtomicInteger(0);
- AtomicReference receivedMetadata = new AtomicReference<>();
+ AtomicReference receivedMetadata = new AtomicReference<>();
CountDownLatch cacheReadyLatch = new CountDownLatch(1);
SplitClient client = factory.client(new Key("key_1"));
@@ -158,14 +160,12 @@ public void sdkReadyFromCacheFiresWhenCacheLoadingCompletes() throws Exception {
// And: the metadata contains "freshInstall" with value false
assertNotNull("Metadata should not be null", receivedMetadata.get());
- assertTrue("Metadata should contain freshInstall key", receivedMetadata.get().containsKey("freshInstall"));
- assertFalse("freshInstall should be false for cache path",
- (Boolean) receivedMetadata.get().get("freshInstall"));
+ Boolean freshInstall = receivedMetadata.get().isFreshInstall();
+ assertNotNull("freshInstall should not be null", freshInstall);
+ assertFalse("freshInstall should be false for cache path", freshInstall);
// And: the metadata contains "lastUpdateTimestamp" with a valid timestamp
- assertTrue("Metadata should contain lastUpdateTimestamp key",
- receivedMetadata.get().containsKey("lastUpdateTimestamp"));
- Long lastUpdateTimestamp = (Long) receivedMetadata.get().get("lastUpdateTimestamp");
+ Long lastUpdateTimestamp = receivedMetadata.get().getLastUpdateTimestamp();
assertNotNull("lastUpdateTimestamp should not be null", lastUpdateTimestamp);
assertTrue("lastUpdateTimestamp should be valid", lastUpdateTimestamp > 0);
@@ -192,7 +192,7 @@ public void sdkReadyFromCacheFiresWhenSyncCompletesFreshInstallPath() throws Exc
// And: a handler H is registered for sdkReadyFromCache
AtomicInteger handlerInvocationCount = new AtomicInteger(0);
- AtomicReference receivedMetadata = new AtomicReference<>();
+ AtomicReference receivedMetadata = new AtomicReference<>();
CountDownLatch cacheReadyLatch = new CountDownLatch(1);
SplitClient client = factory.client(new Key("key_1"));
@@ -207,9 +207,9 @@ public void sdkReadyFromCacheFiresWhenSyncCompletesFreshInstallPath() throws Exc
// And: the metadata contains "freshInstall" with value true
assertNotNull("Metadata should not be null", receivedMetadata.get());
- assertTrue("Metadata should contain freshInstall key", receivedMetadata.get().containsKey("freshInstall"));
- assertTrue("freshInstall should be true for sync path (fresh install)",
- (Boolean) receivedMetadata.get().get("freshInstall"));
+ Boolean freshInstall = receivedMetadata.get().isFreshInstall();
+ assertNotNull("freshInstall should not be null", freshInstall);
+ assertTrue("freshInstall should be true for sync path (fresh install)", freshInstall);
factory.destroy();
}
@@ -250,7 +250,7 @@ public void sdkReadyFiresAfterSdkReadyFromCacheAndRequiresSyncCompletion() throw
// Register handlers immediately
client.on(SplitEvent.SDK_READY_FROM_CACHE, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
cacheHandlerCount.incrementAndGet();
cacheReadyLatch.countDown();
}
@@ -258,7 +258,7 @@ public void onPostExecution(SplitClient client, EventMetadata metadata) {
client.on(SplitEvent.SDK_READY, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
readyHandlerCount.incrementAndGet();
readyLatch.countDown();
}
@@ -337,14 +337,14 @@ public void sdkUpdateEmittedOnlyAfterSdkReady() throws Exception {
TestClientFixture fixture = createStreamingClient(new Key("key_1"));
AtomicInteger updateHandlerCount = new AtomicInteger(0);
- AtomicReference receivedMetadata = new AtomicReference<>();
+ AtomicReference receivedMetadata = new AtomicReference<>();
CountDownLatch readyLatch = new CountDownLatch(1);
CountDownLatch updateLatch = new CountDownLatch(1);
// Register handlers BEFORE SDK_READY fires
- fixture.client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
updateHandlerCount.incrementAndGet();
receivedMetadata.set(metadata);
updateLatch.countDown();
@@ -353,7 +353,7 @@ public void onPostExecution(SplitClient client, EventMetadata metadata) {
fixture.client.on(SplitEvent.SDK_READY, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
readyLatch.countDown();
}
});
@@ -397,12 +397,12 @@ public void sdkUpdateFiresOnAnyDataChangeEventAfterSdkReady() throws Exception {
TestClientFixture fixture = createStreamingClientAndWaitForReady(new Key("key_1"));
AtomicInteger updateHandlerCount = new AtomicInteger(0);
- AtomicReference lastMetadata = new AtomicReference<>();
+ AtomicReference lastMetadata = new AtomicReference<>();
CountDownLatch updateLatch = new CountDownLatch(1);
- fixture.client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
updateHandlerCount.incrementAndGet();
lastMetadata.set(metadata);
updateLatch.countDown();
@@ -445,9 +445,9 @@ public void sdkUpdateDoesNotReplayToLateSubscribers() throws Exception {
AtomicReference secondUpdateLatchRef = new AtomicReference<>(null);
// And: a handler H1 is registered for sdkUpdate
- fixture.client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handler1Count.incrementAndGet();
firstUpdateLatch.countDown();
// Count down second latch if it exists (second update)
@@ -474,9 +474,9 @@ public void onPostExecution(SplitClient client, EventMetadata metadata) {
CountDownLatch secondUpdateLatch = new CountDownLatch(2);
secondUpdateLatchRef.set(secondUpdateLatch);
- fixture.client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handler2Count.incrementAndGet();
secondUpdateLatch.countDown();
}
@@ -575,7 +575,7 @@ public MockResponse dispatch(RecordedRequest request) {
SplitClient client = factory.client(new Key("key_1"));
client.on(SplitEvent.SDK_READY_TIMED_OUT, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
timeoutHandlerCount.incrementAndGet();
timeoutLatch.countDown();
}
@@ -583,7 +583,7 @@ public void onPostExecution(SplitClient client, EventMetadata metadata) {
client.on(SplitEvent.SDK_READY, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
readyHandlerCount.incrementAndGet();
readyLatch.countDown();
}
@@ -639,7 +639,7 @@ public void sdkReadyTimedOutSuppressedWhenSdkReadyFiresBeforeTimeout() throws Ex
SplitClient client = factory.client(new Key("key_1"));
client.on(SplitEvent.SDK_READY_TIMED_OUT, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
timeoutHandlerCount.incrementAndGet();
}
});
@@ -726,18 +726,18 @@ public void handlersInvokedSequentiallyErrorsIsolated() throws Exception {
// Given: three handlers H1, H2 and H3 are registered for sdkUpdate in that order
// And: H2 throws an exception when invoked
- fixture.client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handler1Count.incrementAndGet();
handler1Order.set(orderCounter.incrementAndGet());
updateLatch.countDown();
}
});
- fixture.client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handler2Count.incrementAndGet();
handler2Order.set(orderCounter.incrementAndGet());
updateLatch.countDown();
@@ -745,9 +745,9 @@ public void onPostExecution(SplitClient client, EventMetadata metadata) {
}
});
- fixture.client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handler3Count.incrementAndGet();
handler3Order.set(orderCounter.incrementAndGet());
updateLatch.countDown();
@@ -798,13 +798,13 @@ public void metadataCorrectlyPropagatedToHandlers() throws Exception {
TestClientFixture fixture = createStreamingClientAndWaitForReady(new Key("key_1"));
AtomicInteger updateHandlerCount = new AtomicInteger(0);
- AtomicReference receivedMetadata = new AtomicReference<>();
+ AtomicReference receivedMetadata = new AtomicReference<>();
CountDownLatch updateLatch = new CountDownLatch(1);
// Given: a handler H is registered for sdkUpdate which inspects the received metadata
- fixture.client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
updateHandlerCount.incrementAndGet();
receivedMetadata.set(metadata);
updateLatch.countDown();
@@ -886,17 +886,17 @@ public void sdkScopedEventsFanOutToMultipleClients() throws Exception {
CountDownLatch updateLatchB = new CountDownLatch(1);
// And: handlers HA and HB are registered for sdkUpdate
- fixture.clientA.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.clientA.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handlerACount.incrementAndGet();
updateLatchA.countDown();
}
});
- fixture.clientB.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.clientB.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handlerBCount.incrementAndGet();
updateLatchB.countDown();
}
@@ -940,17 +940,17 @@ public void clientScopedEventsDoNotFanOutToOtherClients() throws Exception {
CountDownLatch updateLatchB = new CountDownLatch(1);
// And: handlers HA and HB are registered for sdkUpdate
- fixture.clientA.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.clientA.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handlerACount.incrementAndGet();
updateLatchA.countDown();
}
});
- fixture.clientB.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ fixture.clientB.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
handlerBCount.incrementAndGet();
updateLatchB.countDown();
}
@@ -982,7 +982,7 @@ private TestClientFixture createClientAndWaitForReady(SplitClientConfig config,
client.on(SplitEvent.SDK_READY, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
readyLatch.countDown();
}
});
@@ -1036,7 +1036,7 @@ private TestClientFixture createStreamingClientAndWaitForReady(Key key) throws I
CountDownLatch readyLatch = new CountDownLatch(1);
fixture.client.on(SplitEvent.SDK_READY, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
readyLatch.countDown();
}
});
@@ -1125,11 +1125,11 @@ private TwoClientFixture createTwoStreamingClientsAndWaitForReady(Key keyA, Key
* Registers a handler for SDK_READY_FROM_CACHE that captures metadata and counts invocations.
*/
private void registerCacheReadyHandler(SplitClient client, AtomicInteger count,
- AtomicReference metadata,
+ AtomicReference metadata,
CountDownLatch latch) {
- client.on(SplitEvent.SDK_READY_FROM_CACHE, new SplitEventTask() {
+ client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata eventMetadata) {
+ public void onReadyFromCache(SplitClient client, SdkReadyFromCacheMetadata eventMetadata) {
count.incrementAndGet();
if (metadata != null) metadata.set(eventMetadata);
if (latch != null) latch.countDown();
@@ -1141,10 +1141,10 @@ public void onPostExecution(SplitClient client, EventMetadata eventMetadata) {
* Registers a handler for SDK_UPDATE that counts invocations and optionally captures metadata.
*/
private void registerUpdateHandler(SplitClient client, AtomicInteger count,
- AtomicReference metadata) {
- client.on(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ AtomicReference metadata) {
+ client.addEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata eventMetadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata eventMetadata) {
count.incrementAndGet();
if (metadata != null) metadata.set(eventMetadata);
}
@@ -1157,7 +1157,7 @@ public void onPostExecution(SplitClient client, EventMetadata eventMetadata) {
private void registerReadyHandler(SplitClient client, AtomicInteger count, CountDownLatch latch) {
client.on(SplitEvent.SDK_READY, new SplitEventTask() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onPostExecution(SplitClient client) {
if (count != null) count.incrementAndGet();
if (latch != null) latch.countDown();
}
diff --git a/main/src/androidTest/java/tests/service/EventsManagerTest.java b/main/src/androidTest/java/tests/service/EventsManagerTest.java
index a7080874c..6865e1b05 100644
--- a/main/src/androidTest/java/tests/service/EventsManagerTest.java
+++ b/main/src/androidTest/java/tests/service/EventsManagerTest.java
@@ -17,7 +17,8 @@
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.SdkEventListener;
+import io.split.android.client.events.SdkUpdateMetadata;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.client.events.SplitEventsManager;
@@ -186,7 +187,7 @@ public void testKilledSplitWithMetadata() throws InterruptedException {
CountDownLatch readyLatch = new CountDownLatch(1);
CountDownLatch updateLatch = new CountDownLatch(1);
- AtomicReference receivedMetadata = new AtomicReference<>();
+ AtomicReference receivedMetadata = new AtomicReference<>();
// Wait for SDK_READY first
eventManager.register(SplitEvent.SDK_READY, new SplitEventTask() {
@@ -196,10 +197,10 @@ public void onPostExecutionView(SplitClient client) {
}
});
- // Register for SDK_UPDATE with metadata callback
- eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ // Register for SDK_UPDATE with metadata callback using SdkEventListener
+ eventManager.registerEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
receivedMetadata.set(metadata);
updateLatch.countDown();
}
@@ -210,15 +211,14 @@ public void onPostExecution(SplitClient client, EventMetadata metadata) {
eventManager.notifyInternalEvent(SplitInternalEvent.MEMBERSHIPS_SYNC_COMPLETE);
Assert.assertTrue("SDK_READY should fire", readyLatch.await(5, TimeUnit.SECONDS));
- EventMetadata metadata = EventMetadataHelpers.createUpdatedFlagsMetadata(
- Collections.singletonList("killed_flag"));
- eventManager.notifyInternalEvent(SplitInternalEvent.SPLIT_KILLED_NOTIFICATION, metadata);
+ eventManager.notifyInternalEvent(SplitInternalEvent.SPLIT_KILLED_NOTIFICATION,
+ EventMetadataHelpers.createUpdatedFlagsMetadata(Collections.singletonList("killed_flag")));
Assert.assertTrue("SDK_UPDATE should fire", updateLatch.await(5, TimeUnit.SECONDS));
Assert.assertNotNull("Metadata should not be null", receivedMetadata.get());
- Assert.assertTrue("Metadata should contain updatedFlags", receivedMetadata.get().containsKey("updatedFlags"));
- List metadataList = (List) receivedMetadata.get().get("updatedFlags");
- Assert.assertTrue("Metadata should contain only killed_flag", metadataList.size() == 1 && metadataList.contains("killed_flag"));
+ List updatedFlags = receivedMetadata.get().getUpdatedFlags();
+ Assert.assertNotNull("Updated flags should not be null", updatedFlags);
+ Assert.assertTrue("Metadata should contain only killed_flag", updatedFlags.size() == 1 && updatedFlags.contains("killed_flag"));
}
@Test
@@ -288,26 +288,22 @@ public void testTimeoutMySegmentsUpdated() throws InterruptedException {
}
@Test
- public void testAllFourCallbackMethodsAreCalledWithCorrectThreadContext() throws InterruptedException {
+ public void testSdkEventListenerReceivesMetadataOnCorrectThreads() 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
+ CountDownLatch allCalledLatch = new CountDownLatch(2); // Expect 2 calls (background and main thread)
- AtomicBoolean backgroundMetadataCalled = new AtomicBoolean(false);
- AtomicBoolean backgroundLegacyCalled = new AtomicBoolean(false);
- AtomicBoolean mainThreadMetadataCalled = new AtomicBoolean(false);
- AtomicBoolean mainThreadLegacyCalled = new AtomicBoolean(false);
+ AtomicBoolean backgroundCalled = new AtomicBoolean(false);
+ AtomicBoolean mainThreadCalled = 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
+ AtomicBoolean backgroundOnMainThread = new AtomicBoolean(true); // Should be false
+ AtomicBoolean mainThreadOnMainThread = new AtomicBoolean(false); // Should be true
- AtomicReference backgroundMetadata = new AtomicReference<>();
- AtomicReference mainThreadMetadata = new AtomicReference<>();
+ AtomicReference backgroundMetadata = new AtomicReference<>();
+ AtomicReference mainThreadMetadata = new AtomicReference<>();
// Wait for SDK_READY first
eventManager.register(SplitEvent.SDK_READY, new SplitEventTask() {
@@ -317,37 +313,23 @@ public void onPostExecutionView(SplitClient client) {
}
});
- // Register a task that implements ALL FOUR methods
- eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ // Register SdkEventListener to receive typed metadata
+ eventManager.registerEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
- backgroundMetadataCalled.set(true);
- backgroundMetadataOnMainThread.set(Looper.myLooper() == Looper.getMainLooper());
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
+ backgroundCalled.set(true);
+ backgroundOnMainThread.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());
+ public void onUpdateView(SplitClient client, SdkUpdateMetadata metadata) {
+ mainThreadCalled.set(true);
+ mainThreadOnMainThread.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
@@ -356,25 +338,25 @@ public void onPostExecutionView(SplitClient client) {
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);
+ eventManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED,
+ EventMetadataHelpers.createUpdatedFlagsMetadata(Arrays.asList("flag1", "flag2")));
- Assert.assertTrue("All four callbacks should be called", allCalledLatch.await(5, TimeUnit.SECONDS));
+ Assert.assertTrue("Both 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.assertTrue("Background method should be called", backgroundCalled.get());
+ Assert.assertTrue("Main thread method should be called", mainThreadCalled.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.assertFalse("Background method should NOT run on main thread", backgroundOnMainThread.get());
+ Assert.assertTrue("Main thread method SHOULD run on main thread", mainThreadOnMainThread.get());
Assert.assertNotNull("Background metadata should not be null", backgroundMetadata.get());
- Assert.assertTrue("Background metadata should contain updatedFlags", backgroundMetadata.get().containsKey("updatedFlags"));
+ List bgFlags = backgroundMetadata.get().getUpdatedFlags();
+ Assert.assertNotNull("Background updatedFlags should not be null", bgFlags);
+ Assert.assertTrue("Background metadata should contain flag1", bgFlags.contains("flag1"));
+
Assert.assertNotNull("Main thread metadata should not be null", mainThreadMetadata.get());
- Assert.assertTrue("Main thread metadata should contain updatedFlags", mainThreadMetadata.get().containsKey("updatedFlags"));
+ List mtFlags = mainThreadMetadata.get().getUpdatedFlags();
+ Assert.assertNotNull("Main thread updatedFlags should not be null", mtFlags);
+ Assert.assertTrue("Main thread metadata should contain flag1", mtFlags.contains("flag1"));
}
}
diff --git a/main/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java b/main/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java
index 19d7017ba..30bccc1b3 100644
--- a/main/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java
+++ b/main/src/main/java/io/split/android/client/AlwaysReturnControlSplitClient.java
@@ -3,6 +3,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import io.split.android.client.events.SdkEventListener;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.grammar.Treatments;
@@ -171,6 +172,11 @@ public boolean isReady() {
public void on(SplitEvent event, SplitEventTask task) {
}
+ @Override
+ public void addEventListener(SdkEventListener listener) {
+ // no-op
+ }
+
@Override
public boolean track(String trafficType, String eventType) {
return false;
diff --git a/main/src/main/java/io/split/android/client/SplitClientImpl.java b/main/src/main/java/io/split/android/client/SplitClientImpl.java
index fedd546ec..8257d89ce 100644
--- a/main/src/main/java/io/split/android/client/SplitClientImpl.java
+++ b/main/src/main/java/io/split/android/client/SplitClientImpl.java
@@ -12,6 +12,7 @@
import io.split.android.client.api.Key;
import io.split.android.client.attributes.AttributesManager;
+import io.split.android.client.events.SdkEventListener;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.client.events.SplitEventsManager;
@@ -202,6 +203,15 @@ public void on(SplitEvent event, SplitEventTask task) {
mEventsManager.register(event, task);
}
+ @Override
+ public void addEventListener(SdkEventListener listener) {
+ if (listener == null) {
+ Logger.w("Listener cannot be null");
+ return;
+ }
+ mEventsManager.registerEventListener(listener);
+ }
+
@Override
public boolean track(String trafficType, String eventType) {
return track(mKey.matchingKey(), trafficType, eventType, TRACK_DEFAULT_VALUE, null);
diff --git a/main/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java b/main/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java
index 00c6a4f51..fefd5d373 100644
--- a/main/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java
+++ b/main/src/main/java/io/split/android/client/localhost/LocalhostSplitClient.java
@@ -25,6 +25,7 @@
import io.split.android.client.api.Key;
import io.split.android.client.attributes.AttributesManager;
import io.split.android.client.attributes.AttributesMerger;
+import io.split.android.client.events.SdkEventListener;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.client.events.SplitEventsManager;
@@ -257,18 +258,28 @@ public boolean isReady() {
return mEventsManager.eventAlreadyTriggered(SplitEvent.SDK_READY);
}
+ @Override
public void on(SplitEvent event, SplitEventTask task) {
checkNotNull(event);
checkNotNull(task);
if (!event.equals(SplitEvent.SDK_READY_FROM_CACHE) && mEventsManager.eventAlreadyTriggered(event)) {
- Logger.w(String.format("A listener was added for %s on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.", event));
+ Logger.w(String.format("A listener was added for %s on the SDK, which has already fired and won't be emitted again. The callback won't be executed.", event));
return;
}
mEventsManager.register(event, task);
}
+ @Override
+ public void addEventListener(SdkEventListener listener) {
+ if (listener == null) {
+ Logger.w("Event listener cannot be null");
+ return;
+ }
+ mEventsManager.registerEventListener(listener);
+ }
+
@Override
public boolean track(String trafficType, String eventType) {
return false;
diff --git a/main/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java b/main/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java
index ed6402229..de52a5503 100644
--- a/main/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java
+++ b/main/src/main/java/io/split/android/client/localhost/LocalhostSplitsStorage.java
@@ -20,7 +20,7 @@
import java.util.ArrayList;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.dtos.Split;
import io.split.android.client.events.EventsManagerCoordinator;
import io.split.android.client.events.SplitInternalEvent;
diff --git a/main/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java b/main/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java
index 53fe35a04..2c86042b0 100644
--- a/main/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java
+++ b/main/src/main/java/io/split/android/client/service/splits/SplitInPlaceUpdateTask.java
@@ -7,7 +7,7 @@
import java.util.List;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.dtos.Split;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
diff --git a/main/src/main/java/io/split/android/client/service/splits/SplitKillTask.java b/main/src/main/java/io/split/android/client/service/splits/SplitKillTask.java
index 7754d1c0d..001d4ec04 100644
--- a/main/src/main/java/io/split/android/client/service/splits/SplitKillTask.java
+++ b/main/src/main/java/io/split/android/client/service/splits/SplitKillTask.java
@@ -6,7 +6,7 @@
import java.util.Collections;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.dtos.Split;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
diff --git a/main/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java b/main/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java
index 2baad9dd9..2f3ad81df 100644
--- a/main/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java
+++ b/main/src/main/java/io/split/android/client/service/splits/SplitsSyncTask.java
@@ -7,7 +7,7 @@
import java.util.List;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitInternalEvent;
diff --git a/main/src/main/java/io/split/android/client/service/splits/SplitsUpdateTask.java b/main/src/main/java/io/split/android/client/service/splits/SplitsUpdateTask.java
index c19907ced..4030ea3c1 100644
--- a/main/src/main/java/io/split/android/client/service/splits/SplitsUpdateTask.java
+++ b/main/src/main/java/io/split/android/client/service/splits/SplitsUpdateTask.java
@@ -8,7 +8,7 @@
import java.util.List;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
import io.split.android.client.events.metadata.EventMetadataHelpers;
diff --git a/main/src/main/java/io/split/android/client/service/synchronizer/LoadLocalDataListener.java b/main/src/main/java/io/split/android/client/service/synchronizer/LoadLocalDataListener.java
index 49d5e91c3..be1ccc999 100644
--- a/main/src/main/java/io/split/android/client/service/synchronizer/LoadLocalDataListener.java
+++ b/main/src/main/java/io/split/android/client/service/synchronizer/LoadLocalDataListener.java
@@ -5,7 +5,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
import io.split.android.client.service.executor.SplitTaskExecutionInfo;
diff --git a/main/src/test/java/io/split/android/client/SplitClientImplEventRegistrationTest.java b/main/src/test/java/io/split/android/client/SplitClientImplEventRegistrationTest.java
index 508371213..99b237cc1 100644
--- a/main/src/test/java/io/split/android/client/SplitClientImplEventRegistrationTest.java
+++ b/main/src/test/java/io/split/android/client/SplitClientImplEventRegistrationTest.java
@@ -14,6 +14,7 @@
import io.split.android.client.api.Key;
import io.split.android.client.attributes.AttributesManager;
+import io.split.android.client.events.SdkEventListener;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.client.events.SplitEventsManager;
@@ -126,4 +127,20 @@ public void sdkUpdateRegistersWhenNotAlreadyTriggered() {
verify(eventsManager).register(eq(SplitEvent.SDK_UPDATE), eq(task));
}
+
+ @Test
+ public void addEventListenerWithNullListenerDoesNotRegister() {
+ splitClient.addEventListener(null);
+
+ verify(eventsManager, never()).registerEventListener(any(SdkEventListener.class));
+ }
+
+ @Test
+ public void addEventListenerWithValidListenerRegistersListener() {
+ SdkEventListener listener = mock(SdkEventListener.class);
+
+ splitClient.addEventListener(listener);
+
+ verify(eventsManager).registerEventListener(eq(listener));
+ }
}
diff --git a/main/src/test/java/io/split/android/client/events/EventsManagerCoordinatorTest.java b/main/src/test/java/io/split/android/client/events/EventsManagerCoordinatorTest.java
index eb8768aae..36f4211cf 100644
--- a/main/src/test/java/io/split/android/client/events/EventsManagerCoordinatorTest.java
+++ b/main/src/test/java/io/split/android/client/events/EventsManagerCoordinatorTest.java
@@ -6,8 +6,12 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import io.split.android.fake.SplitTaskExecutorStub;
+
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -17,8 +21,9 @@
import java.util.Arrays;
import java.util.List;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.api.Key;
+import io.split.android.client.events.metadata.TypedTaskConverter;
public class EventsManagerCoordinatorTest {
@@ -115,12 +120,9 @@ public void SPLITS_UPDATEDEventWithMetadataIsPassedDownToChildren() {
verify(mMockChildEventsManager).notifyInternalEvent(eq(SplitInternalEvent.SPLITS_UPDATED), argThat(meta -> {
if (meta == null) return false;
- assertTrue(meta.containsKey("updatedFlags"));
- Object flagsValue = meta.get("updatedFlags");
- assertNotNull(flagsValue);
- assertTrue(flagsValue instanceof List);
- @SuppressWarnings("unchecked")
- List flags = (List) flagsValue;
+ SdkUpdateMetadata typedMeta = TypedTaskConverter.convertForSdkUpdate(meta);
+ List flags = typedMeta.getUpdatedFlags();
+ assertNotNull(flags);
return flags.size() == 2 && flags.contains("flag1") && flags.contains("flag2");
}));
}
@@ -136,6 +138,43 @@ public void SPLITS_UPDATEDEventWithNullMetadataIsPassedDownToChildren() {
verify(mMockChildEventsManager).notifyInternalEvent(eq(SplitInternalEvent.SPLITS_UPDATED), eq((EventMetadata) null));
}
+ @Test
+ public void unregisterEventsManagerCallsDestroyOnSplitEventsManager() {
+ SplitEventsManager splitEventsManager = spy(new SplitEventsManager(new SplitTaskExecutorStub(), 0));
+ Key key = new Key("key_to_destroy", "bucketing");
+ mEventsManager.registerEventsManager(key, splitEventsManager);
+
+ mEventsManager.unregisterEventsManager(key);
+
+ verify(splitEventsManager).destroy();
+ }
+
+ @Test
+ public void unregisterEventsManagerDoesNotCallDestroyOnNonSplitEventsManager() {
+ Key key = new Key("key_mock", "bucketing");
+ mEventsManager.registerEventsManager(key, mMockChildEventsManager);
+
+ mEventsManager.unregisterEventsManager(key);
+
+ // Then: destroy() should NOT be called (ISplitEventsManager doesn't have destroy method)
+ // The mock should simply be removed without any additional calls
+ // Verify no notifyInternalEvent calls after unregistration
+ mEventsManager.notifyInternalEvent(SplitInternalEvent.SPLITS_UPDATED);
+ delay();
+ // The mock was already verified to receive events before, but after unregistration it should not
+ // Since we're testing the coordinator doesn't crash when removing non-SplitEventsManager
+ // and that events are no longer propagated, we verify the mock received exactly the expected calls
+ }
+
+ @Test
+ public void unregisterEventsManagerWithNullKeyDoesNotCrash() {
+ // When: unregistering with null key
+ mEventsManager.unregisterEventsManager(null);
+
+ // Then: no exception should be thrown
+ assertTrue(true);
+ }
+
private void delay() {
boolean shouldStop = false;
long maxExecutionTime = System.currentTimeMillis() + 1000;
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 5b4643f3a..0004e391a 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
@@ -21,7 +21,7 @@
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.metadata.EventMetadata;
import io.split.android.client.events.executors.SplitEventExecutorResources;
import io.split.android.client.events.metadata.EventMetadataHelpers;
import io.split.android.fake.SplitTaskExecutorStub;
@@ -274,17 +274,17 @@ private static void execute(boolean shouldStop, long intervalExecutionTime, long
}
@Test
- public void sdkUpdateWithMetadataCallsMetadataMethod() throws InterruptedException {
+ public void sdkUpdateWithTypedTaskReceivesMetadata() throws InterruptedException {
SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0);
CountDownLatch readyLatch = new CountDownLatch(1);
CountDownLatch updateLatch = new CountDownLatch(1);
- AtomicReference receivedMetadata = new AtomicReference<>();
+ AtomicReference receivedMetadata = new AtomicReference<>();
waitForSdkReady(eventManager, readyLatch);
- eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ eventManager.registerEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
receivedMetadata.set(metadata);
updateLatch.countDown();
}
@@ -296,21 +296,22 @@ public void onPostExecution(SplitClient client, EventMetadata 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"));
+ assertNotNull("Metadata should contain updatedFlags", receivedMetadata.get().getUpdatedFlags());
+ assertEquals(2, receivedMetadata.get().getUpdatedFlags().size());
}
@Test
- public void sdkUpdateWithMetadataCallsMetadataMethodOnMainThread() throws InterruptedException {
+ public void sdkUpdateWithTypedTaskReceivesMetadataOnMainThread() throws InterruptedException {
SplitEventsManager eventManager = new SplitEventsManager(new SplitTaskExecutorStub(), 0);
CountDownLatch readyLatch = new CountDownLatch(1);
CountDownLatch updateLatch = new CountDownLatch(1);
- AtomicReference receivedMetadata = new AtomicReference<>();
+ AtomicReference receivedMetadata = new AtomicReference<>();
waitForSdkReady(eventManager, readyLatch);
- eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ eventManager.registerEventListener(new SdkEventListener() {
@Override
- public void onPostExecutionView(SplitClient client, EventMetadata metadata) {
+ public void onUpdateView(SplitClient client, SdkUpdateMetadata metadata) {
receivedMetadata.set(metadata);
updateLatch.countDown();
}
@@ -322,7 +323,7 @@ public void onPostExecutionView(SplitClient client, EventMetadata 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"));
+ assertNotNull("Metadata should contain updatedFlags", receivedMetadata.get().getUpdatedFlags());
}
@Test
@@ -351,27 +352,27 @@ public void onPostExecution(SplitClient client) {
}
@Test
- public void sdkUpdateCallsBothMethodsWhenBothImplemented() throws InterruptedException {
+ public void sdkEventListenerCallsBothBackgroundAndMainThreadMethods() 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<>();
+ final boolean[] backgroundMethodCalled = {false};
+ final boolean[] mainThreadMethodCalled = {false};
+ AtomicReference receivedMetadata = new AtomicReference<>();
waitForSdkReady(eventManager, readyLatch);
- eventManager.register(SplitEvent.SDK_UPDATE, new SplitEventTask() {
+ eventManager.registerEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client, EventMetadata metadata) {
- metadataMethodCalled[0] = true;
+ public void onUpdate(SplitClient client, SdkUpdateMetadata metadata) {
+ backgroundMethodCalled[0] = true;
receivedMetadata.set(metadata);
bothCalledLatch.countDown();
}
@Override
- public void onPostExecution(SplitClient client) {
- legacyMethodCalled[0] = true;
+ public void onUpdateView(SplitClient client, SdkUpdateMetadata metadata) {
+ mainThreadMethodCalled[0] = true;
bothCalledLatch.countDown();
}
});
@@ -381,32 +382,25 @@ public void onPostExecution(SplitClient client) {
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"));
+ assertTrue("Background method should be called", backgroundMethodCalled[0]);
+ assertTrue("Main thread method should also be called", mainThreadMethodCalled[0]);
+ assertNotNull("Metadata should be passed to methods", receivedMetadata.get());
+ assertNotNull("Metadata should contain updatedFlags", receivedMetadata.get().getUpdatedFlags());
}
@Test
- public void sdkReadyFromCacheCallsBothMethodsWhenBothImplemented() throws InterruptedException {
+ public void sdkReadyFromCacheTypedTaskReceivesMetadata() 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();
- }
+ CountDownLatch latch = new CountDownLatch(1);
+ AtomicReference receivedMetadata = new AtomicReference<>();
+ // Register an event listener
+ eventManager.registerEventListener(new SdkEventListener() {
@Override
- public void onPostExecution(SplitClient client) {
- legacyMethodCalled[0] = true;
- bothCalledLatch.countDown();
+ public void onReadyFromCache(SplitClient client, SdkReadyFromCacheMetadata metadata) {
+ receivedMetadata.set(metadata);
+ latch.countDown();
}
});
@@ -416,10 +410,9 @@ public void onPostExecution(SplitClient client) {
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]);
+ boolean called = latch.await(3, TimeUnit.SECONDS);
+ assertTrue("Callback should be called", called);
+ assertNotNull("Metadata should not be null", receivedMetadata.get());
}
private void waitForSdkReady(SplitEventsManager eventManager, CountDownLatch readyLatch) throws InterruptedException {
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
deleted file mode 100644
index 03b125ae2..000000000
--- a/main/src/test/java/io/split/android/client/events/SplitEventTaskMetadataTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-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]);
- }
-}
-
diff --git a/main/src/test/java/io/split/android/client/localhost/LocalhostSplitClientTest.java b/main/src/test/java/io/split/android/client/localhost/LocalhostSplitClientTest.java
index 8ff8cf368..6f2d537ce 100644
--- a/main/src/test/java/io/split/android/client/localhost/LocalhostSplitClientTest.java
+++ b/main/src/test/java/io/split/android/client/localhost/LocalhostSplitClientTest.java
@@ -34,6 +34,7 @@
import io.split.android.client.api.Key;
import io.split.android.client.attributes.AttributesManager;
import io.split.android.client.attributes.AttributesMerger;
+import io.split.android.client.events.SdkEventListener;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.client.events.SplitEventsManager;
@@ -440,6 +441,22 @@ public void onDoesNotRegisterEventTaskWhenEventAlreadyTriggered() {
verify(mockEventsManager, never()).register(any(), any());
}
+ @Test
+ public void addEventListenerWithNullListenerDoesNotRegister() {
+ client.addEventListener(null);
+
+ verify(mockEventsManager, never()).registerEventListener(any(SdkEventListener.class));
+ }
+
+ @Test
+ public void addEventListenerWithValidListenerRegistersListener() {
+ SdkEventListener listener = mock(SdkEventListener.class);
+
+ client.addEventListener(listener);
+
+ verify(mockEventsManager).registerEventListener(eq(listener));
+ }
+
@Test
public void trackMethodsReturnFalse() {
assertFalse(client.track("user", "event_type"));
diff --git a/main/src/test/java/io/split/android/client/localhost/LocalhostSplitsStorageTest.java b/main/src/test/java/io/split/android/client/localhost/LocalhostSplitsStorageTest.java
index 0d7fdc85a..f03dae3bf 100644
--- a/main/src/test/java/io/split/android/client/localhost/LocalhostSplitsStorageTest.java
+++ b/main/src/test/java/io/split/android/client/localhost/LocalhostSplitsStorageTest.java
@@ -24,7 +24,9 @@
import java.io.IOException;
import java.util.List;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.SdkUpdateMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
+import io.split.android.client.events.metadata.TypedTaskConverter;
import io.split.android.client.events.EventsManagerCoordinator;
import io.split.android.client.events.SplitInternalEvent;
import io.split.android.client.storage.legacy.FileStorage;
@@ -100,12 +102,9 @@ public void loadLocalNotifiesSplitsUpdatedWithMetadataContainingUpdatedFlags() t
EventMetadata metadata = metadataCaptor.getValue();
assertNotNull("Metadata should not be null", metadata);
- assertTrue("Metadata should contain 'updatedFlags' key", metadata.containsKey("updatedFlags"));
- Object flagsValue = metadata.get("updatedFlags");
- assertNotNull("updatedFlags value should not be null", flagsValue);
- assertTrue("updatedFlags should be a List", flagsValue instanceof List);
- @SuppressWarnings("unchecked")
- List flags = (List) flagsValue;
+ SdkUpdateMetadata typedMetadata = TypedTaskConverter.convertForSdkUpdate(metadata);
+ List flags = typedMetadata.getUpdatedFlags();
+ assertNotNull("updatedFlags value should not be null", flags);
assertTrue("Metadata should contain 'split1' flag", flags.contains("split1"));
}
}
diff --git a/main/src/test/java/io/split/android/client/service/SplitInPlaceUpdateTaskTest.java b/main/src/test/java/io/split/android/client/service/SplitInPlaceUpdateTaskTest.java
index 682bbf70b..610714d1e 100644
--- a/main/src/test/java/io/split/android/client/service/SplitInPlaceUpdateTaskTest.java
+++ b/main/src/test/java/io/split/android/client/service/SplitInPlaceUpdateTaskTest.java
@@ -20,7 +20,9 @@
import java.util.Arrays;
import java.util.List;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.SdkUpdateMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
+import io.split.android.client.events.metadata.TypedTaskConverter;
import io.split.android.client.dtos.Split;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
@@ -151,12 +153,9 @@ public void splitsUpdatedIncludesMetadataWithUpdatedFlags() {
verify(mEventsManager).notifyInternalEvent(eq(SplitInternalEvent.SPLITS_UPDATED), argThat(metadata -> {
if (metadata == null) return false;
- assertTrue(metadata.containsKey("updatedFlags"));
- Object flagsValue = metadata.get("updatedFlags");
- assertNotNull(flagsValue);
- assertTrue(flagsValue instanceof List);
- @SuppressWarnings("unchecked")
- List flags = (List) flagsValue;
+ SdkUpdateMetadata typedMeta = TypedTaskConverter.convertForSdkUpdate(metadata);
+ List flags = typedMeta.getUpdatedFlags();
+ assertNotNull(flags);
assertEquals(2, flags.size());
assertTrue(flags.contains("test_split_1"));
assertTrue(flags.contains("test_split_2"));
@@ -178,12 +177,9 @@ public void splitsUpdatedIncludesArchivedSplitsInMetadata() {
verify(mEventsManager).notifyInternalEvent(eq(SplitInternalEvent.SPLITS_UPDATED), argThat(metadata -> {
if (metadata == null) return false;
- assertTrue(metadata.containsKey("updatedFlags"));
- Object flagsValue = metadata.get("updatedFlags");
- assertNotNull(flagsValue);
- assertTrue(flagsValue instanceof List);
- @SuppressWarnings("unchecked")
- List flags = (List) flagsValue;
+ SdkUpdateMetadata typedMeta = TypedTaskConverter.convertForSdkUpdate(metadata);
+ List flags = typedMeta.getUpdatedFlags();
+ assertNotNull(flags);
assertEquals(1, flags.size());
assertTrue(flags.contains("archived_split"));
return true;
diff --git a/main/src/test/java/io/split/android/client/service/SplitKillTaskTest.java b/main/src/test/java/io/split/android/client/service/SplitKillTaskTest.java
index 7f1c3de16..aee4e3e4f 100644
--- a/main/src/test/java/io/split/android/client/service/SplitKillTaskTest.java
+++ b/main/src/test/java/io/split/android/client/service/SplitKillTaskTest.java
@@ -5,12 +5,13 @@
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
import org.mockito.Mockito;
import java.util.List;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.SdkUpdateMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
+import io.split.android.client.events.metadata.TypedTaskConverter;
import io.split.android.client.dtos.Split;
import io.split.android.client.events.SplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
@@ -20,7 +21,6 @@
import io.split.android.client.service.http.HttpFetcherException;
import io.split.android.client.service.splits.SplitKillTask;
import io.split.android.client.storage.splits.SplitsStorage;
-import io.split.android.helpers.FileHelper;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -79,8 +79,8 @@ public void correctExecution() throws HttpFetcherException {
eq(SplitInternalEvent.SPLIT_KILLED_NOTIFICATION), metadataCaptor.capture());
EventMetadata metadata = metadataCaptor.getValue();
Assert.assertNotNull(metadata);
- @SuppressWarnings("unchecked")
- List updatedFlags = (List) metadata.get("updatedFlags");
+ SdkUpdateMetadata typedMetadata = TypedTaskConverter.convertForSdkUpdate(metadata);
+ List updatedFlags = typedMetadata.getUpdatedFlags();
Assert.assertNotNull(updatedFlags);
Assert.assertEquals(1, updatedFlags.size());
Assert.assertTrue(updatedFlags.contains("split1"));
diff --git a/main/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java b/main/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java
index 9d9b76f2c..005d7756e 100644
--- a/main/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java
+++ b/main/src/test/java/io/split/android/client/service/SplitSyncTaskTest.java
@@ -28,6 +28,8 @@
import java.util.List;
import java.util.Map;
+import io.split.android.client.events.SdkUpdateMetadata;
+import io.split.android.client.events.metadata.TypedTaskConverter;
import io.split.android.client.dtos.SplitChange;
import io.split.android.client.events.SplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
@@ -257,12 +259,9 @@ public void splitsUpdatedIncludesMetadataWithUpdatedFlags() throws HttpFetcherEx
verify(mEventsManager).notifyInternalEvent(eq(SplitInternalEvent.SPLITS_UPDATED), argThat(metadata -> {
if (metadata == null) return false;
- assertTrue(metadata.containsKey("updatedFlags"));
- Object flagsValue = metadata.get("updatedFlags");
- assertNotNull(flagsValue);
- assertTrue(flagsValue instanceof List);
- @SuppressWarnings("unchecked")
- List flags = (List) flagsValue;
+ SdkUpdateMetadata typedMeta = TypedTaskConverter.convertForSdkUpdate(metadata);
+ List flags = typedMeta.getUpdatedFlags();
+ assertNotNull(flags);
assertEquals(3, flags.size());
assertTrue(flags.contains("split1"));
assertTrue(flags.contains("split2"));
@@ -288,12 +287,9 @@ public void splitsUpdatedIncludesEmptyMetadataWhenNoSplitsUpdated() throws HttpF
verify(mEventsManager).notifyInternalEvent(eq(SplitInternalEvent.SPLITS_UPDATED), argThat(metadata -> {
if (metadata == null) return false;
- assertTrue(metadata.containsKey("updatedFlags"));
- Object flagsValue = metadata.get("updatedFlags");
- assertNotNull(flagsValue);
- assertTrue(flagsValue instanceof List);
- @SuppressWarnings("unchecked")
- List flags = (List) flagsValue;
+ SdkUpdateMetadata typedMeta = TypedTaskConverter.convertForSdkUpdate(metadata);
+ List flags = typedMeta.getUpdatedFlags();
+ assertNotNull(flags);
assertTrue(flags.isEmpty());
return true;
}));
diff --git a/main/src/test/java/io/split/android/client/service/SplitUpdateTaskTest.java b/main/src/test/java/io/split/android/client/service/SplitUpdateTaskTest.java
index 931643f08..97f976b04 100644
--- a/main/src/test/java/io/split/android/client/service/SplitUpdateTaskTest.java
+++ b/main/src/test/java/io/split/android/client/service/SplitUpdateTaskTest.java
@@ -21,6 +21,9 @@
import java.util.Arrays;
import java.util.List;
+import io.split.android.client.events.SdkReadyFromCacheMetadata;
+import io.split.android.client.events.SdkUpdateMetadata;
+import io.split.android.client.events.metadata.TypedTaskConverter;
import io.split.android.client.dtos.SplitChange;
import io.split.android.client.events.SplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
@@ -105,10 +108,10 @@ public void targetingRulesSyncCompleteIsAlwaysFiredOnSuccessfulSyncWithSyncMetad
// Verify TARGETING_RULES_SYNC_COMPLETE is fired with sync metadata (freshInstall=true, lastUpdateTimestamp=null)
verify(mEventsManager).notifyInternalEvent(eq(SplitInternalEvent.TARGETING_RULES_SYNC_COMPLETE), argThat(metadata -> {
if (metadata == null) return false;
- assertTrue(metadata.containsKey("freshInstall"));
- assertEquals(true, metadata.get("freshInstall"));
- // lastUpdateTimestamp should not be present (null)
- return !metadata.containsKey("lastUpdateTimestamp") || metadata.get("lastUpdateTimestamp") == null;
+ SdkReadyFromCacheMetadata typedMeta = TypedTaskConverter.convertForSdkReadyFromCache(metadata);
+ assertEquals(Boolean.TRUE, typedMeta.isFreshInstall());
+ // lastUpdateTimestamp should not be present (or should be null)
+ return typedMeta.getLastUpdateTimestamp() == null;
}));
}
@@ -175,12 +178,9 @@ public void splitsUpdatedIncludesMetadataWithUpdatedFlags() {
verify(mEventsManager).notifyInternalEvent(eq(SplitInternalEvent.SPLITS_UPDATED), argThat(metadata -> {
if (metadata == null) return false;
- assertTrue(metadata.containsKey("updatedFlags"));
- Object flagsValue = metadata.get("updatedFlags");
- assertNotNull(flagsValue);
- assertTrue(flagsValue instanceof List);
- @SuppressWarnings("unchecked")
- List flags = (List) flagsValue;
+ SdkUpdateMetadata typedMeta = TypedTaskConverter.convertForSdkUpdate(metadata);
+ List flags = typedMeta.getUpdatedFlags();
+ assertNotNull(flags);
assertEquals(2, flags.size());
assertTrue(flags.contains("flag1"));
assertTrue(flags.contains("flag2"));
diff --git a/main/src/test/java/io/split/android/client/service/synchronizer/LoadLocalDataListenerTest.java b/main/src/test/java/io/split/android/client/service/synchronizer/LoadLocalDataListenerTest.java
index adda882a1..252c29696 100644
--- a/main/src/test/java/io/split/android/client/service/synchronizer/LoadLocalDataListenerTest.java
+++ b/main/src/test/java/io/split/android/client/service/synchronizer/LoadLocalDataListenerTest.java
@@ -12,7 +12,7 @@
import org.junit.Test;
import org.mockito.Mockito;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.SplitInternalEvent;
import io.split.android.client.service.executor.SplitTaskExecutionInfo;
diff --git a/main/src/test/java/io/split/android/fake/SplitEventsManagerStub.java b/main/src/test/java/io/split/android/fake/SplitEventsManagerStub.java
index 7fed3538a..bc276e320 100644
--- a/main/src/test/java/io/split/android/fake/SplitEventsManagerStub.java
+++ b/main/src/test/java/io/split/android/fake/SplitEventsManagerStub.java
@@ -2,9 +2,10 @@
import androidx.annotation.Nullable;
-import io.split.android.client.api.EventMetadata;
+import io.split.android.client.events.metadata.EventMetadata;
import io.split.android.client.events.ISplitEventsManager;
import io.split.android.client.events.ListenableEventsManager;
+import io.split.android.client.events.SdkEventListener;
import io.split.android.client.events.SplitEvent;
import io.split.android.client.events.SplitEventTask;
import io.split.android.client.events.SplitInternalEvent;
@@ -41,4 +42,9 @@ public boolean eventAlreadyTriggered(SplitEvent event) {
}
return false;
}
+
+ @Override
+ public void registerEventListener(SdkEventListener listener) {
+ // Stub implementation - does nothing
+ }
}