Skip to content

Commit 7e0ff15

Browse files
author
Ian Bird
committed
Refactor to allow multiple initialization listeners to be attached
1 parent 110fd52 commit 7e0ff15

File tree

4 files changed

+67
-30
lines changed

4 files changed

+67
-30
lines changed

sdk/src/main/java/com/uid2/UID2Manager.kt

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ import kotlinx.coroutines.flow.flow
4141
import kotlinx.coroutines.flow.retryWhen
4242
import kotlinx.coroutines.flow.single
4343
import kotlinx.coroutines.launch
44+
import kotlinx.coroutines.runBlocking
45+
import kotlinx.coroutines.sync.Mutex
46+
import kotlinx.coroutines.sync.withLock
4447

4548
/**
4649
* A listener interface allowing the consumer to be notified when either the identity or status of the identity changes
@@ -99,22 +102,6 @@ public class UID2Manager internal constructor(
99102
*/
100103
public var onIdentityChangedListener: UID2ManagerIdentityChangedListener? = null
101104

102-
/**
103-
* Gets or sets a listener which can be used to determine if the [UID2Manager] instance has finished initializing.
104-
* Initializing includes any time required to restore a previously persisted [UID2Identity] from storage.
105-
*
106-
* If this property is set *after* initialization is complete, the callback will be invoked immediately.
107-
*/
108-
public var onInitialized: (() -> Unit)? = null
109-
set(value) {
110-
field = value
111-
112-
// If we've already finished initializing, we should immediately invoke the callback.
113-
if (initialized.isCompleted) {
114-
value?.invoke()
115-
}
116-
}
117-
118105
private val _state = MutableStateFlow<UID2ManagerState>(Loading)
119106

120107
/**
@@ -123,8 +110,10 @@ public class UID2Manager internal constructor(
123110
public val state: Flow<UID2ManagerState> = _state.asStateFlow()
124111

125112
// The Job responsible for initialising the manager. This will include de-serialising our initial state from
126-
// storage.
113+
// storage. We allow consumers to attach a listener to detect when this Job is complete.
127114
private var initialized: Job
115+
private val onInitializedListeners = mutableListOf<() -> Unit>()
116+
private val initializedLock = Mutex()
128117

129118
// An active Job that is scheduled to refresh the current identity
130119
private var refreshJob: Job? = null
@@ -177,6 +166,35 @@ public class UID2Manager internal constructor(
177166
checkIdentityRefresh()
178167
}
179168

169+
/**
170+
* Adds a listener which can be used to determine if the [UID2Manager] instance has finished initializing.
171+
* Initializing includes any time required to restore a previously persisted [UID2Identity] from storage.
172+
*
173+
* If a listener is added *after* initialization is complete, the callback will be invoked immediately.
174+
*/
175+
public fun addOnInitializedListener(listener: () -> Unit): Unit = runBlocking {
176+
initializedLock.withLock {
177+
// If we've already finished initializing, we should immediately invoke the callback.
178+
if (initialized.isCompleted) {
179+
listener()
180+
} else {
181+
onInitializedListeners += listener
182+
}
183+
}
184+
}
185+
186+
/**
187+
* Removes a listener which was previously added via [addOnInitializedListener].
188+
*
189+
* If the listener has already been invoked after initialization was complete, it will already be removed internally
190+
* to avoid leaking.
191+
*/
192+
public fun removeOnInitializedListener(listener: () -> Unit): Unit = runBlocking {
193+
initializedLock.withLock {
194+
onInitializedListeners -= listener
195+
}
196+
}
197+
180198
init {
181199
initialized = scope.launch {
182200
// Attempt to load the Identity from storage. If successful, we can notify any observers.
@@ -188,8 +206,7 @@ public class UID2Manager internal constructor(
188206
validateAndSetIdentity(it.first, it.second, false)
189207
}
190208

191-
// If we have a callback provided, invoke it.
192-
onInitialized?.invoke()
209+
onInitialized()
193210
}
194211
}
195212

@@ -309,6 +326,19 @@ public class UID2Manager internal constructor(
309326
}
310327
}
311328

329+
/**
330+
* After initialization is complete, all the attached listeners will be invoked.
331+
*/
332+
private suspend fun onInitialized() {
333+
initializedLock.withLock {
334+
onInitializedListeners.forEach { listener ->
335+
listener()
336+
}
337+
338+
onInitializedListeners.clear()
339+
}
340+
}
341+
312342
private fun refreshIdentityInternal(identity: UID2Identity) = scope.launch {
313343
try {
314344
refreshToken(identity).retryWhen { _, attempt ->

sdk/src/test/java/com/uid2/UID2ManagerTest.kt

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,30 @@ class UID2ManagerTest {
8080

8181
@Test
8282
fun `reports when initialization is complete`() = runTest(testDispatcher) {
83-
var isInitialized = false
84-
val onInitialized = { isInitialized = true }
83+
val listenerCount = 5
84+
val listenerResults = ArrayList<Boolean>().apply {
85+
for (i in 0..listenerCount) {
86+
add(false)
87+
}
88+
}
8589

8690
val manager = UID2Manager(client, storageManager, timeUtils, inputUtils, testDispatcher, false, logger).apply {
8791
this.checkExpiration = false
88-
this.onInitialized = onInitialized
92+
93+
// Add the required listeners.
94+
for (i in 0..listenerCount) {
95+
addOnInitializedListener { listenerResults[i] = true }
96+
}
8997
}
9098

9199
// Verify that the manager invokes our callback after it's been able to load the identity from storage.
92-
assertFalse(isInitialized)
100+
assertTrue(listenerResults.all { !it })
93101
testDispatcher.scheduler.advanceUntilIdle()
94-
assertTrue(isInitialized)
102+
assertTrue(listenerResults.all { it })
95103

96-
// Reset our state and re-assign the manager's callback. Verify that even though initialization is complete, our
97-
// callback is invoked immediately.
98-
isInitialized = false
99-
manager.onInitialized = onInitialized
104+
// Create a new listener that we add after initialization is complete. Verify that it's called immediately.
105+
var isInitialized = false
106+
manager.addOnInitializedListener { isInitialized = true }
100107
assertTrue(isInitialized)
101108
}
102109

securesignals-gma/src/main/java/com/uid2/securesignals/gma/UID2MediationAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class UID2MediationAdapter : RtbAdapter() {
4848
// After we've asked to initialize the manager, we should wait until it's complete before reporting success.
4949
// This will potentially allow any previously persisted identity to be fully restored before we allow any
5050
// signals to be collected.
51-
UID2Manager.getInstance().onInitialized = initializationCompleteCallback::onInitializationSucceeded
51+
UID2Manager.getInstance().addOnInitializedListener(initializationCompleteCallback::onInitializationSucceeded)
5252
}
5353

5454
/**

securesignals-ima/src/main/java/com/uid2/securesignals/ima/UID2SecureSignalsAdapter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class UID2SecureSignalsAdapter : SecureSignalsAdapter {
4444
// After we've asked to initialize the manager, we should wait until it's complete before reporting success.
4545
// This will potentially allow any previously persisted identity to be fully restored before we allow any
4646
// signals to be collected.
47-
UID2Manager.getInstance().onInitialized = callback::onSuccess
47+
UID2Manager.getInstance().addOnInitializedListener(callback::onSuccess)
4848
}
4949

5050
/**

0 commit comments

Comments
 (0)