Skip to content

Commit a75f81f

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

File tree

4 files changed

+55
-30
lines changed

4 files changed

+55
-30
lines changed

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

+37-19
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,25 @@ 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): UID2Manager = apply {
176+
runBlocking {
177+
initializedLock.withLock {
178+
// If we've already finished initializing, we should immediately invoke the callback.
179+
if (initialized.isCompleted) {
180+
listener()
181+
} else {
182+
onInitializedListeners += listener
183+
}
184+
}
185+
}
186+
}
187+
180188
init {
181189
initialized = scope.launch {
182190
// Attempt to load the Identity from storage. If successful, we can notify any observers.
@@ -188,8 +196,7 @@ public class UID2Manager internal constructor(
188196
validateAndSetIdentity(it.first, it.second, false)
189197
}
190198

191-
// If we have a callback provided, invoke it.
192-
onInitialized?.invoke()
199+
onInitialized()
193200
}
194201
}
195202

@@ -309,6 +316,17 @@ public class UID2Manager internal constructor(
309316
}
310317
}
311318

319+
/**
320+
* After initialization is complete, all the attached listeners will be invoked.
321+
*/
322+
private suspend fun onInitialized() {
323+
initializedLock.withLock {
324+
while (onInitializedListeners.isNotEmpty()) {
325+
onInitializedListeners.removeFirst().invoke()
326+
}
327+
}
328+
}
329+
312330
private fun refreshIdentityInternal(identity: UID2Identity) = scope.launch {
313331
try {
314332
refreshToken(identity).retryWhen { _, attempt ->

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

+16-9
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

+1-1
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

+1-1
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)