@@ -54,6 +54,8 @@ import kotlinx.coroutines.async
54
54
import kotlinx.coroutines.cancel
55
55
import kotlinx.coroutines.flow.MutableStateFlow
56
56
import kotlinx.coroutines.flow.collect
57
+ import kotlinx.coroutines.flow.first
58
+ import kotlinx.coroutines.launch
57
59
import kotlinx.coroutines.runBlocking
58
60
import kotlinx.coroutines.sync.Mutex
59
61
import kotlinx.coroutines.sync.withLock
@@ -72,6 +74,9 @@ internal interface FirebaseDataConnectInternal : FirebaseDataConnect {
72
74
73
75
val lazyGrpcClient: SuspendingLazy <DataConnectGrpcClient >
74
76
val lazyQueryManager: SuspendingLazy <QueryManager >
77
+
78
+ suspend fun awaitAuthReady ()
79
+ suspend fun awaitAppCheckReady ()
75
80
}
76
81
77
82
internal class FirebaseDataConnectImpl (
@@ -107,11 +112,18 @@ internal class FirebaseDataConnectImpl(
107
112
SupervisorJob () +
108
113
nonBlockingDispatcher +
109
114
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
+ }
112
121
}
113
122
)
114
123
124
+ private val authProviderAvailable = MutableStateFlow (false )
125
+ private val appCheckProviderAvailable = MutableStateFlow (false )
126
+
115
127
// Protects `closed`, `grpcClient`, `emulatorSettings`, and `queryManager`.
116
128
private val mutex = Mutex ()
117
129
@@ -121,29 +133,49 @@ internal class FirebaseDataConnectImpl(
121
133
// All accesses to this variable _must_ have locked `mutex`.
122
134
private var closed = false
123
135
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
+ }
134
155
}
156
+ }
135
157
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
+ }
146
177
}
178
+ }
147
179
148
180
private val lazyGrpcRPCs =
149
181
SuspendingLazy (mutex) {
@@ -181,8 +213,8 @@ internal class FirebaseDataConnectImpl(
181
213
val grpcMetadata =
182
214
DataConnectGrpcMetadata .forSystemVersions(
183
215
firebaseApp = app,
184
- dataConnectAuth = lazyDataConnectAuth.getLocked() ,
185
- dataConnectAppCheck = lazyDataConnectAppCheck.getLocked() ,
216
+ dataConnectAuth = dataConnectAuth ,
217
+ dataConnectAppCheck = dataConnectAppCheck ,
186
218
connectorLocation = config.location,
187
219
parentLogger = logger,
188
220
)
@@ -210,8 +242,8 @@ internal class FirebaseDataConnectImpl(
210
242
projectId = projectId,
211
243
connector = config,
212
244
grpcRPCs = lazyGrpcRPCs.getLocked(),
213
- dataConnectAuth = lazyDataConnectAuth.getLocked() ,
214
- dataConnectAppCheck = lazyDataConnectAppCheck.getLocked() ,
245
+ dataConnectAuth = dataConnectAuth ,
246
+ dataConnectAppCheck = dataConnectAppCheck ,
215
247
logger = Logger (" DataConnectGrpcClient" ).apply { debug { " created by $instanceId " } },
216
248
)
217
249
}
@@ -397,8 +429,8 @@ internal class FirebaseDataConnectImpl(
397
429
398
430
// Close Auth and AppCheck synchronously to avoid race conditions with auth callbacks.
399
431
// 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()
402
434
403
435
// Start the job to asynchronously close the gRPC client.
404
436
while (true ) {
0 commit comments