@@ -54,6 +54,8 @@ import kotlinx.coroutines.async
5454import kotlinx.coroutines.cancel
5555import kotlinx.coroutines.flow.MutableStateFlow
5656import kotlinx.coroutines.flow.collect
57+ import kotlinx.coroutines.flow.first
58+ import kotlinx.coroutines.launch
5759import kotlinx.coroutines.runBlocking
5860import kotlinx.coroutines.sync.Mutex
5961import kotlinx.coroutines.sync.withLock
@@ -72,6 +74,9 @@ internal interface FirebaseDataConnectInternal : FirebaseDataConnect {
7274
7375 val lazyGrpcClient: SuspendingLazy <DataConnectGrpcClient >
7476 val lazyQueryManager: SuspendingLazy <QueryManager >
77+
78+ suspend fun awaitAuthReady ()
79+ suspend fun awaitAppCheckReady ()
7580}
7681
7782internal class FirebaseDataConnectImpl (
@@ -107,11 +112,18 @@ internal class FirebaseDataConnectImpl(
107112 SupervisorJob () +
108113 nonBlockingDispatcher +
109114 CoroutineName (instanceId) +
110- CoroutineExceptionHandler { _, throwable ->
111- logger.warn(throwable) { " uncaught exception from a coroutine" }
115+ CoroutineExceptionHandler { context, throwable ->
116+ logger.warn(throwable) {
117+ val coroutineName = context[CoroutineName ]?.name
118+ " WARNING: uncaught exception from coroutine named \" $coroutineName \" " +
119+ " (error code jszxcbe37k)"
120+ }
112121 }
113122 )
114123
124+ private val authProviderAvailable = MutableStateFlow (false )
125+ private val appCheckProviderAvailable = MutableStateFlow (false )
126+
115127 // Protects `closed`, `grpcClient`, `emulatorSettings`, and `queryManager`.
116128 private val mutex = Mutex ()
117129
@@ -121,29 +133,49 @@ internal class FirebaseDataConnectImpl(
121133 // All accesses to this variable _must_ have locked `mutex`.
122134 private var closed = false
123135
124- private val lazyDataConnectAuth =
125- SuspendingLazy (mutex) {
126- if (closed) throw IllegalStateException (" FirebaseDataConnect instance has been closed" )
127- DataConnectAuth (
128- deferredAuthProvider = deferredAuthProvider,
129- parentCoroutineScope = coroutineScope,
130- blockingDispatcher = blockingDispatcher,
131- logger = Logger (" DataConnectAuth" ).apply { debug { " created by $instanceId " } },
132- )
133- .apply { initialize() }
136+ private val dataConnectAuth: DataConnectAuth =
137+ DataConnectAuth (
138+ deferredAuthProvider = deferredAuthProvider,
139+ parentCoroutineScope = coroutineScope,
140+ blockingDispatcher = blockingDispatcher,
141+ logger = Logger (" DataConnectAuth" ).apply { debug { " created by $instanceId " } },
142+ )
143+
144+ override suspend fun awaitAuthReady () {
145+ authProviderAvailable.first { it }
146+ }
147+
148+ init {
149+ coroutineScope.launch(CoroutineName (" DataConnectAuth initializer for $instanceId " )) {
150+ dataConnectAuth.initialize()
151+ dataConnectAuth.providerAvailable.collect { isProviderAvailable ->
152+ logger.debug { " authProviderAvailable=$isProviderAvailable " }
153+ authProviderAvailable.value = isProviderAvailable
154+ }
134155 }
156+ }
135157
136- private val lazyDataConnectAppCheck =
137- SuspendingLazy (mutex) {
138- if (closed) throw IllegalStateException (" FirebaseDataConnect instance has been closed" )
139- DataConnectAppCheck (
140- deferredAppCheckTokenProvider = deferredAppCheckProvider,
141- parentCoroutineScope = coroutineScope,
142- blockingDispatcher = blockingDispatcher,
143- logger = Logger (" DataConnectAppCheck" ).apply { debug { " created by $instanceId " } },
144- )
145- .apply { initialize() }
158+ private val dataConnectAppCheck: DataConnectAppCheck =
159+ DataConnectAppCheck (
160+ deferredAppCheckTokenProvider = deferredAppCheckProvider,
161+ parentCoroutineScope = coroutineScope,
162+ blockingDispatcher = blockingDispatcher,
163+ logger = Logger (" DataConnectAppCheck" ).apply { debug { " created by $instanceId " } },
164+ )
165+
166+ override suspend fun awaitAppCheckReady () {
167+ appCheckProviderAvailable.first { it }
168+ }
169+
170+ init {
171+ coroutineScope.launch(CoroutineName (" DataConnectAppCheck initializer for $instanceId " )) {
172+ dataConnectAppCheck.initialize()
173+ dataConnectAppCheck.providerAvailable.collect { isProviderAvailable ->
174+ logger.debug { " appCheckProviderAvailable=$isProviderAvailable " }
175+ appCheckProviderAvailable.value = isProviderAvailable
176+ }
146177 }
178+ }
147179
148180 private val lazyGrpcRPCs =
149181 SuspendingLazy (mutex) {
@@ -181,8 +213,8 @@ internal class FirebaseDataConnectImpl(
181213 val grpcMetadata =
182214 DataConnectGrpcMetadata .forSystemVersions(
183215 firebaseApp = app,
184- dataConnectAuth = lazyDataConnectAuth.getLocked() ,
185- dataConnectAppCheck = lazyDataConnectAppCheck.getLocked() ,
216+ dataConnectAuth = dataConnectAuth ,
217+ dataConnectAppCheck = dataConnectAppCheck ,
186218 connectorLocation = config.location,
187219 parentLogger = logger,
188220 )
@@ -210,8 +242,8 @@ internal class FirebaseDataConnectImpl(
210242 projectId = projectId,
211243 connector = config,
212244 grpcRPCs = lazyGrpcRPCs.getLocked(),
213- dataConnectAuth = lazyDataConnectAuth.getLocked() ,
214- dataConnectAppCheck = lazyDataConnectAppCheck.getLocked() ,
245+ dataConnectAuth = dataConnectAuth ,
246+ dataConnectAppCheck = dataConnectAppCheck ,
215247 logger = Logger (" DataConnectGrpcClient" ).apply { debug { " created by $instanceId " } },
216248 )
217249 }
@@ -397,8 +429,8 @@ internal class FirebaseDataConnectImpl(
397429
398430 // Close Auth and AppCheck synchronously to avoid race conditions with auth callbacks.
399431 // Since close() is re-entrant, this is safe even if they have already been closed.
400- lazyDataConnectAuth.initializedValueOrNull? .close()
401- lazyDataConnectAppCheck.initializedValueOrNull? .close()
432+ dataConnectAuth .close()
433+ dataConnectAppCheck .close()
402434
403435 // Start the job to asynchronously close the gRPC client.
404436 while (true ) {
0 commit comments