@@ -41,6 +41,9 @@ import kotlinx.coroutines.flow.flow
4141import kotlinx.coroutines.flow.retryWhen
4242import kotlinx.coroutines.flow.single
4343import 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 ->
0 commit comments