@@ -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,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 ->
0 commit comments