From 0775acdc8ee0c80d06a703e5c2f9c14532241f6e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 19 Aug 2025 15:37:11 +0530 Subject: [PATCH 1/2] [ECO-5479] Renamed all Live Object/Objects references to plain Object/Objects --- lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java | 2 +- .../main/java/io/ably/lib/objects/RealtimeObjects.java | 2 +- .../io/ably/lib/objects/state/ObjectsStateChange.java | 4 ++-- .../java/io/ably/lib/objects/state/ObjectsStateEvent.java | 2 +- .../main/java/io/ably/lib/objects/type/ObjectUpdate.java | 2 +- .../kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt | 4 ++-- .../src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt | 8 ++++---- .../src/main/kotlin/io/ably/lib/objects/ObjectsState.kt | 4 ++-- live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt | 2 ++ .../objects/integration/helpers/fixtures/MapFixtures.kt | 4 ++-- 10 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java b/lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java index ef30ab7f8..be149843b 100644 --- a/lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java +++ b/lib/src/main/java/io/ably/lib/objects/ObjectsPlugin.java @@ -38,7 +38,7 @@ public interface ObjectsPlugin { * * @param channelName the name of the channel whose state has changed. * @param state the new state of the channel. - * @param hasObjects flag indicates whether the channel has any associated live objects. + * @param hasObjects flag indicates whether the channel has any associated objects. */ void handleStateChange(@NotNull String channelName, @NotNull ChannelState state, boolean hasObjects); diff --git a/lib/src/main/java/io/ably/lib/objects/RealtimeObjects.java b/lib/src/main/java/io/ably/lib/objects/RealtimeObjects.java index 950b41bf6..6e111b304 100644 --- a/lib/src/main/java/io/ably/lib/objects/RealtimeObjects.java +++ b/lib/src/main/java/io/ably/lib/objects/RealtimeObjects.java @@ -13,7 +13,7 @@ /** * The RealtimeObjects interface provides methods to interact with live data objects, * such as maps and counters, in a real-time environment. It supports both synchronous - * and asynchronous operations for retrieving and creating live objects. + * and asynchronous operations for retrieving and creating objects. * *

Implementations of this interface must be thread-safe as they may be accessed * from multiple threads concurrently. diff --git a/lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java b/lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java index 7b3a7e1e3..180645f3c 100644 --- a/lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java +++ b/lib/src/main/java/io/ably/lib/objects/state/ObjectsStateChange.java @@ -6,7 +6,7 @@ public interface ObjectsStateChange { /** - * Subscribes to a specific Live Objects synchronization state event. + * Subscribes to a specific Objects synchronization state event. * *

This method registers the provided listener to be notified when the specified * synchronization state event occurs. The returned subscription can be used to @@ -40,7 +40,7 @@ public interface ObjectsStateChange { void offAll(); /** - * Interface for receiving notifications about Live Objects synchronization state changes. + * Interface for receiving notifications about Objects synchronization state changes. *

* Implement this interface and register it with an ObjectsStateEmitter to be notified * when synchronization state transitions occur. diff --git a/lib/src/main/java/io/ably/lib/objects/state/ObjectsStateEvent.java b/lib/src/main/java/io/ably/lib/objects/state/ObjectsStateEvent.java index 4fa01a173..1aa27203a 100644 --- a/lib/src/main/java/io/ably/lib/objects/state/ObjectsStateEvent.java +++ b/lib/src/main/java/io/ably/lib/objects/state/ObjectsStateEvent.java @@ -1,7 +1,7 @@ package io.ably.lib.objects.state; /** - * Represents the synchronization state of Ably Live Objects. + * Represents the synchronization state of Ably Objects. *

* This enum is used to notify listeners about state changes in the synchronization process. * Clients can register an {@link ObjectsStateChange.Listener} to receive these events. diff --git a/lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java b/lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java index 6df47cf99..8ee1e1578 100644 --- a/lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java +++ b/lib/src/main/java/io/ably/lib/objects/type/ObjectUpdate.java @@ -5,7 +5,7 @@ /** * Abstract base class for all LiveMap/LiveCounter update notifications. * Provides common structure for updates that occur on LiveMap and LiveCounter objects. - * Contains the update data that describes what changed in the live object. + * Contains the update data that describes what changed in the object. * Spec: RTLO4b4 */ public abstract class ObjectUpdate { diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt index 0b00a1680..00401c50e 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt @@ -20,12 +20,12 @@ import java.util.concurrent.CancellationException /** * Default implementation of RealtimeObjects interface. - * Provides the core functionality for managing live objects on a channel. + * Provides the core functionality for managing objects on a channel. */ internal class DefaultRealtimeObjects(internal val channelName: String, internal val adapter: ObjectsAdapter): RealtimeObjects { private val tag = "DefaultRealtimeObjects" /** - * @spec RTO3 - Objects pool storing all live objects by object ID + * @spec RTO3 - Objects pool storing all objects by object ID */ internal val objectsPool = ObjectsPool(this) diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt index a874d6dd6..28ee839e0 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsPool.kt @@ -28,9 +28,9 @@ internal object ObjectsPoolDefaults { internal const val ROOT_OBJECT_ID = "root" /** - * ObjectsPool manages a pool of live objects for a channel. + * ObjectsPool manages a pool of objects for a channel. * - * @spec RTO3 - Maintains an objects pool for all live objects on the channel + * @spec RTO3 - Maintains an objects pool for all objects on the channel */ internal class ObjectsPool( private val realtimeObjects: DefaultRealtimeObjects @@ -39,7 +39,7 @@ internal class ObjectsPool( /** * ConcurrentHashMap for thread-safe access from public APIs in LiveMap and LiveCounter. - * @spec RTO3a - Pool storing all live objects by object ID + * @spec RTO3a - Pool storing all ably objects by object ID */ private val pool = ConcurrentHashMap() @@ -57,7 +57,7 @@ internal class ObjectsPool( } /** - * Gets a live object from the pool by object ID. + * Gets an object from the pool by object ID. */ internal fun get(objectId: String): BaseRealtimeObject? { return pool[objectId] diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt index f56782613..00335953b 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt @@ -27,14 +27,14 @@ private val objectsStateToEventMap = mapOf( ) /** - * An interface for managing and communicating changes in the synchronization state of live objects. + * An interface for managing and communicating changes in the synchronization state of objects. * * Implementations should ensure thread-safe event emission and proper synchronization * between state change notifications. */ internal interface HandlesObjectsStateChange { /** - * Handles changes in the state of live objects by notifying all registered listeners. + * Handles changes in the state of objects by notifying all registered listeners. * Implementations should ensure thread-safe event emission to both internal and public listeners. * Makes sure every event is processed in the order they were received. * @param newState The new state of the objects, SYNCING or SYNCED. diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt index 3e136163e..6cfbb6f6e 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt @@ -4,6 +4,7 @@ import io.ably.lib.types.AblyException import io.ably.lib.types.ErrorInfo import io.ably.lib.util.Log import kotlinx.coroutines.* +import org.jetbrains.annotations.NotNull import java.nio.charset.StandardCharsets import java.util.concurrent.CancellationException @@ -65,6 +66,7 @@ internal class ObjectsAsyncScope(channelName: String) { private val scope = CoroutineScope(Dispatchers.Default + CoroutineName(tag) + SupervisorJob()) + @NotNull internal fun launchWithCallback(callback: ObjectsCallback, block: suspend () -> T) { scope.launch { try { diff --git a/live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt b/live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt index 8499eefc2..b7979310c 100644 --- a/live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt +++ b/live-objects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt @@ -8,7 +8,7 @@ import io.ably.lib.objects.integration.helpers.RestObjects * Initializes a comprehensive test fixture object tree on the specified channel. * * This method creates a predetermined object hierarchy rooted at a "root" map object, - * establishing references between different types of live objects to enable comprehensive testing. + * establishing references between different types of objects to enable comprehensive testing. * * **Object Tree Structure:** * ``` @@ -71,7 +71,7 @@ internal fun RestObjects.initializeRootMap(channelName: String) { /** * Creates a comprehensive test fixture object tree on the specified channel using * - * This method establishes a hierarchical structure of live objects for testing map operations, + * This method establishes a hierarchical structure of objects for testing map operations, * creating various types of objects and establishing references between them. * * **Object Tree Structure:** From 915f3054c468bf762872d1be3fb3cdfa5767eae5 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Tue, 19 Aug 2025 15:50:55 +0530 Subject: [PATCH 2/2] [ECO-5479] Defined and implemented ObjectLifecycleChange interface - Updated LiveMap and LiveCounter to extend ObjectLifecycleChange interface - Implemented ObjectLifecycleCoordinator by extending ObjectLifecycleChange - Updated testObjectDelete with relevant assertions for ObjectLifecycleEvent.DELETED --- .../objects/type/ObjectLifecycleChange.java | 69 +++++++++++++++ .../objects/type/ObjectLifecycleEvent.java | 16 ++++ .../lib/objects/type/counter/LiveCounter.java | 3 +- .../io/ably/lib/objects/type/map/LiveMap.java | 3 +- .../io/ably/lib/objects/ObjectsState.kt | 3 +- .../main/kotlin/io/ably/lib/objects/Utils.kt | 2 - .../lib/objects/type/BaseRealtimeObject.kt | 17 ++-- .../ably/lib/objects/type/ObjectLifecycle.kt | 84 +++++++++++++++++++ .../LiveCounterChangeCoordinator.kt | 2 +- .../type/livemap/LiveMapChangeCoordinator.kt | 2 +- .../integration/DefaultRealtimeObjectsTest.kt | 22 +++++ 11 files changed, 208 insertions(+), 15 deletions(-) create mode 100644 lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java create mode 100644 lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleEvent.java create mode 100644 live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt diff --git a/lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java b/lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java new file mode 100644 index 000000000..c8d0f5745 --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleChange.java @@ -0,0 +1,69 @@ +package io.ably.lib.objects.type; + +import io.ably.lib.objects.ObjectsSubscription; +import org.jetbrains.annotations.NonBlocking; +import org.jetbrains.annotations.NotNull; + +/** + * Interface for managing subscriptions to Object lifecycle events. + *

+ * This interface provides methods to subscribe to and manage notifications about significant lifecycle + * changes that occur to Object, such as deletion. More events can be added in the future. + * Multiple listeners can be registered independently, and each can be managed separately. + *

+ * Lifecycle events are different from data update events - they represent changes + * to the object's existence state rather than changes to the object's data content. + * + * @see ObjectLifecycleEvent for the available lifecycle events + */ +public interface ObjectLifecycleChange { + /** + * Subscribes to a specific Object lifecycle event. + * + *

This method registers the provided listener to be notified when the specified + * lifecycle event occurs. The returned subscription can be used to + * unsubscribe later when the notifications are no longer needed. + * + * @param event the lifecycle event to subscribe to + * @param listener the listener that will be called when the event occurs + * @return a subscription object that can be used to unsubscribe from the event + */ + @NonBlocking + ObjectsSubscription on(@NotNull ObjectLifecycleEvent event, @NotNull ObjectLifecycleChange.Listener listener); + + /** + * Unsubscribes the specified listener from all lifecycle events. + * + *

After calling this method, the provided listener will no longer receive + * any lifecycle event notifications. + * + * @param listener the listener to unregister from all events + */ + @NonBlocking + void off(@NotNull ObjectLifecycleChange.Listener listener); + + /** + * Unsubscribes all listeners from all lifecycle events. + * + *

After calling this method, no listeners will receive any lifecycle + * event notifications until new listeners are registered. + */ + @NonBlocking + void offAll(); + + /** + * Interface for receiving notifications about Object lifecycle changes. + *

+ * Implement this interface and register it with an ObjectLifecycleChange provider + * to be notified when lifecycle events occur, such as object creation or deletion. + */ + @FunctionalInterface + interface Listener { + /** + * Called when a lifecycle event occurs. + * + * @param lifecycleEvent The lifecycle event that occurred + */ + void onLifecycleEvent(@NotNull ObjectLifecycleEvent lifecycleEvent); + } +} diff --git a/lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleEvent.java b/lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleEvent.java new file mode 100644 index 000000000..7a2d1aa7d --- /dev/null +++ b/lib/src/main/java/io/ably/lib/objects/type/ObjectLifecycleEvent.java @@ -0,0 +1,16 @@ +package io.ably.lib.objects.type; + +/** + * Represents lifecycle events for an Ably Object. + *

+ * This enum notifies listeners about significant lifecycle changes that occur to an Object during its lifetime. + * Clients can register a {@link ObjectLifecycleChange.Listener} to receive these events. + */ +public enum ObjectLifecycleEvent { + /** + * Indicates that an Object has been deleted (tombstoned). + * Emitted once when the object is tombstoned server-side (i.e., deleted and no longer addressable). + * Not re-emitted during client-side garbage collection of tombstones. + */ + DELETED +} diff --git a/lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java b/lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java index c23ccc91b..958cf05b1 100644 --- a/lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java +++ b/lib/src/main/java/io/ably/lib/objects/type/counter/LiveCounter.java @@ -1,6 +1,7 @@ package io.ably.lib.objects.type.counter; import io.ably.lib.objects.ObjectsCallback; +import io.ably.lib.objects.type.ObjectLifecycleChange; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NonBlocking; import org.jetbrains.annotations.NotNull; @@ -11,7 +12,7 @@ * It allows incrementing, decrementing, and retrieving the current value of the counter, * both synchronously and asynchronously. */ -public interface LiveCounter extends LiveCounterChange { +public interface LiveCounter extends LiveCounterChange, ObjectLifecycleChange { /** * Increments the value of the counter by the specified amount. diff --git a/lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java b/lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java index 46c336360..f180fe168 100644 --- a/lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java +++ b/lib/src/main/java/io/ably/lib/objects/type/map/LiveMap.java @@ -1,6 +1,7 @@ package io.ably.lib.objects.type.map; import io.ably.lib.objects.ObjectsCallback; +import io.ably.lib.objects.type.ObjectLifecycleChange; import org.jetbrains.annotations.Blocking; import org.jetbrains.annotations.NonBlocking; import org.jetbrains.annotations.Contract; @@ -14,7 +15,7 @@ * The LiveMap interface provides methods to interact with a live, real-time map structure. * It supports both synchronous and asynchronous operations for managing key-value pairs. */ -public interface LiveMap extends LiveMapChange { +public interface LiveMap extends LiveMapChange, ObjectLifecycleChange { /** * Retrieves the value associated with the specified key. diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt index 00335953b..cdd742ec0 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/ObjectsState.kt @@ -99,7 +99,8 @@ private class ObjectsStateEmitter : EventEmitter launchWithCallback(callback: ObjectsCallback, block: suspend () -> T) { scope.launch { try { diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt index 7d76e7b76..fa94e0a59 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/type/BaseRealtimeObject.kt @@ -27,7 +27,7 @@ internal val ObjectUpdate.noOp get() = this.update == null internal abstract class BaseRealtimeObject( internal val objectId: String, // // RTLO3a internal val objectType: ObjectType, -) { +) : ObjectLifecycleCoordinator() { protected open val tag = "BaseRealtimeObject" @@ -92,7 +92,7 @@ internal abstract class BaseRealtimeObject( if (isTombstoned) { // this object is tombstoned so the operation cannot be applied - return; + return } applyObjectOperation(objectOperation, objectMessage) // RTLC7d } @@ -115,7 +115,7 @@ internal abstract class BaseRealtimeObject( internal fun validateObjectId(objectId: String?) { if (this.objectId != objectId) { - throw objectError("Invalid object: incoming objectId=${objectId}; $objectType objectId=$objectId") + throw objectError("Invalid object: incoming objectId=$objectId; $objectType objectId=${this.objectId}") } } @@ -129,7 +129,8 @@ internal abstract class BaseRealtimeObject( isTombstoned = true tombstonedAt = serialTimestamp?: System.currentTimeMillis() val update = clearData() - // TODO: Emit BaseRealtimeObject lifecycle events + // Emit object lifecycle event for deletion + objectLifecycleChanged(ObjectLifecycle.Deleted) return update } @@ -142,13 +143,13 @@ internal abstract class BaseRealtimeObject( } /** - * Validates that the provided object state is compatible with this live object. + * Validates that the provided object state is compatible with this object. * Checks object ID, type-specific validations, and any included create operations. */ abstract fun validate(state: ObjectState) /** - * Applies an object state received during synchronization to this live object. + * Applies an object state received during synchronization to this object. * This method should update the internal data structure with the complete state * received from the server. * @@ -159,7 +160,7 @@ internal abstract class BaseRealtimeObject( abstract fun applyObjectState(objectState: ObjectState, message: ObjectMessage): ObjectUpdate /** - * Applies an operation to this live object. + * Applies an operation to this object. * This method handles the specific operation actions (e.g., update, remove) * by modifying the underlying data structure accordingly. * @@ -185,7 +186,7 @@ internal abstract class BaseRealtimeObject( abstract fun clearData(): ObjectUpdate /** - * Notifies subscribers about changes made to this live object. Propagates updates through the + * Notifies subscribers about changes made to this object. Propagates updates through the * appropriate manager after converting the generic update map to type-specific update objects. * Spec: RTLO4b4c */ diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt new file mode 100644 index 000000000..70abdea85 --- /dev/null +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/type/ObjectLifecycle.kt @@ -0,0 +1,84 @@ +package io.ably.lib.objects.type + +import io.ably.lib.objects.ObjectsSubscription +import io.ably.lib.util.EventEmitter +import io.ably.lib.util.Log + +/** + * Internal enum representing object lifecycle states + */ +internal enum class ObjectLifecycle { + Created, + Active, + Deleted +} + +/** + * Maps internal ObjectLifecycle values to their corresponding public ObjectLifecycleEvent values. + * Used to determine which events should be emitted when lifecycle changes occur. + * CREATED and ACTIVE map to null (no public event), while DELETED maps to the public DELETED event. + */ +private val objectLifecycleToEventMap = mapOf( + ObjectLifecycle.Created to null, + ObjectLifecycle.Active to null, + ObjectLifecycle.Deleted to ObjectLifecycleEvent.DELETED +) + +/** + * An interface for managing and communicating changes in the lifecycle state of objects. + * + * Implementations should ensure thread-safe event emission and proper lifecycle + * event notifications. + */ +internal interface HandlesObjectLifecycleChange { + /** + * Handles changes in the lifecycle of objects by notifying all registered listeners. + * Implementations should ensure thread-safe event emission to both internal and public listeners. + * Makes sure every event is processed in the order they were received. + * @param newLifecycle The new lifecycle state of the object. + */ + fun objectLifecycleChanged(newLifecycle: ObjectLifecycle) + + /** + * Disposes all registered lifecycle change listeners and cancels any pending operations. + * Should be called when the associated object is no longer needed. + */ + fun disposeObjectLifecycleListeners() +} + +internal abstract class ObjectLifecycleCoordinator : ObjectLifecycleChange, HandlesObjectLifecycleChange { + private val tag = "ObjectLifecycleCoordinator" + // EventEmitter for users of the library + private val objectLifecycleEmitter = ObjectLifecycleEmitter() + + override fun on(event: ObjectLifecycleEvent, listener: ObjectLifecycleChange.Listener): ObjectsSubscription { + objectLifecycleEmitter.on(event, listener) + return ObjectsSubscription { + objectLifecycleEmitter.off(event, listener) + } + } + + override fun off(listener: ObjectLifecycleChange.Listener) = objectLifecycleEmitter.off(listener) + + override fun offAll() = objectLifecycleEmitter.off() + + override fun objectLifecycleChanged(newLifecycle: ObjectLifecycle) { + objectLifecycleToEventMap[newLifecycle]?.let { objectLifecycleEvent -> + objectLifecycleEmitter.emit(objectLifecycleEvent) + } + } + + override fun disposeObjectLifecycleListeners() = offAll() +} + +private class ObjectLifecycleEmitter : EventEmitter() { + private val tag = "ObjectLifecycleEmitter" + override fun apply(listener: ObjectLifecycleChange.Listener?, event: ObjectLifecycleEvent?, vararg args: Any?) { + try { + event?.let { listener?.onLifecycleEvent(it) } + ?: Log.w(tag, "Null event passed to ObjectLifecycleChange listener callback") + } catch (t: Throwable) { + Log.e(tag, "Error occurred while executing listener callback for event: $event", t) + } + } +} diff --git a/live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterChangeCoordinator.kt b/live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterChangeCoordinator.kt index 0ea58f389..a1940dc04 100644 --- a/live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterChangeCoordinator.kt +++ b/live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterChangeCoordinator.kt @@ -43,7 +43,7 @@ private class LiveCounterChangeEmitter : EventEmitter() + // Remove the "referencedCounter" from the root map val refCounter = rootMap.get("referencedCounter")?.asLiveCounter assertNotNull(refCounter) @@ -178,6 +182,10 @@ class DefaultRealtimeObjectsTest : IntegrationTest() { refCounter.subscribe { event -> counterUpdates.add(event.update.amount) } + // Subscribe to lifecycle events for this counter + refCounter.on(ObjectLifecycleEvent.DELETED) { event -> + lifecycleEvents.add(event) + } // Simulate the deletion of the referencedCounter object channel.objects.simulateObjectDelete(refCounter as DefaultLiveCounter) @@ -195,6 +203,10 @@ class DefaultRealtimeObjectsTest : IntegrationTest() { referencedMap.subscribe { event -> mapUpdates.add(event.update) } + // Subscribe to lifecycle events for this map + referencedMap.on(ObjectLifecycleEvent.DELETED) { event -> + lifecycleEvents.add(event) + } // Simulate the deletion of the referencedMap object channel.objects.simulateObjectDelete(referencedMap as DefaultLiveMap) @@ -216,6 +228,10 @@ class DefaultRealtimeObjectsTest : IntegrationTest() { valuesMap.subscribe { event -> valuesMapUpdates.add(event.update) } + // Subscribe to lifecycle events for this map + valuesMap.on(ObjectLifecycleEvent.DELETED) { event -> + lifecycleEvents.add(event) + } // Simulate the deletion of the valuesMap object channel.objects.simulateObjectDelete(valuesMap as DefaultLiveMap) @@ -230,5 +246,11 @@ class DefaultRealtimeObjectsTest : IntegrationTest() { updatedValuesMap.values.forEach { change -> assertEquals(LiveMapUpdate.Change.REMOVED, change) } + + // Assert lifecycle events + assertEquals(3, lifecycleEvents.size) // Should have received 3 DELETED lifecycle events + lifecycleEvents.forEach { event -> + assertEquals(ObjectLifecycleEvent.DELETED, event) // All events should be DELETED + } } }