5
5
@file:JvmName(" android" )
6
6
package dev.gitlive.firebase.firestore
7
7
8
+ import com.google.android.gms.tasks.TaskExecutors
9
+ import com.google.firebase.firestore.MemoryCacheSettings
10
+ import com.google.firebase.firestore.MemoryEagerGcSettings
11
+ import com.google.firebase.firestore.MemoryLruGcSettings
8
12
import com.google.firebase.firestore.MetadataChanges
13
+ import com.google.firebase.firestore.PersistentCacheSettings
9
14
import dev.gitlive.firebase.Firebase
10
15
import dev.gitlive.firebase.FirebaseApp
16
+ import kotlinx.coroutines.channels.ProducerScope
11
17
import dev.gitlive.firebase.firestore.Source.*
12
18
import kotlinx.coroutines.channels.awaitClose
13
19
import kotlinx.coroutines.flow.Flow
14
20
import kotlinx.coroutines.flow.callbackFlow
15
21
import kotlinx.coroutines.runBlocking
16
22
import kotlinx.coroutines.tasks.await
17
- import kotlinx.serialization.Serializable
23
+ import java.util.concurrent.ConcurrentHashMap
24
+ import java.util.concurrent.Executor
18
25
import com.google.firebase.firestore.FieldPath as AndroidFieldPath
19
26
import com.google.firebase.firestore.Filter as AndroidFilter
20
27
import com.google.firebase.firestore.Query as AndroidQuery
28
+ import com.google.firebase.firestore.firestoreSettings as androidFirestoreSettings
29
+ import com.google.firebase.firestore.memoryCacheSettings as androidMemoryCacheSettings
30
+ import com.google.firebase.firestore.memoryEagerGcSettings as androidMemoryEagerGcSettings
31
+ import com.google.firebase.firestore.memoryLruGcSettings as androidMemoryLruGcSettings
32
+ import com.google.firebase.firestore.persistentCacheSettings as androidPersistentCacheSettings
21
33
22
34
actual val Firebase .firestore get() =
23
35
FirebaseFirestore (com.google.firebase.firestore.FirebaseFirestore .getInstance())
24
36
25
37
actual fun Firebase.firestore (app : FirebaseApp ) =
26
38
FirebaseFirestore (com.google.firebase.firestore.FirebaseFirestore .getInstance(app.android))
27
39
28
- actual class FirebaseFirestore (val android : com.google.firebase.firestore.FirebaseFirestore ) {
40
+ val LocalCacheSettings .android: com.google.firebase.firestore.LocalCacheSettings get() = when (this ) {
41
+ is LocalCacheSettings .Persistent -> androidPersistentCacheSettings {
42
+ setSizeBytes(sizeBytes)
43
+ }
44
+ is LocalCacheSettings .Memory -> androidMemoryCacheSettings {
45
+ setGcSettings(
46
+ when (garbaseCollectorSettings) {
47
+ is MemoryGarbageCollectorSettings .Eager -> androidMemoryEagerGcSettings { }
48
+ is MemoryGarbageCollectorSettings .LRUGC -> androidMemoryLruGcSettings {
49
+ setSizeBytes(garbaseCollectorSettings.sizeBytes)
50
+ }
51
+ }
52
+ )
53
+ }
54
+ }
55
+
56
+ // Since on iOS Callback threads are set as settings, we store the settings explicitly here as well
57
+ private val callbackExecutorMap = ConcurrentHashMap < com.google.firebase.firestore.FirebaseFirestore , Executor > ()
58
+
59
+ actual typealias NativeFirebaseFirestore = com.google.firebase.firestore.FirebaseFirestore
60
+ internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual val native : NativeFirebaseFirestore ) {
61
+
62
+ actual var settings: FirebaseFirestoreSettings
63
+ get() = with (native.firestoreSettings) {
64
+ FirebaseFirestoreSettings (
65
+ isSslEnabled,
66
+ host,
67
+ cacheSettings?.let { localCacheSettings ->
68
+ when (localCacheSettings) {
69
+ is MemoryCacheSettings -> {
70
+ val garbageCollectionSettings = when (val settings = localCacheSettings.garbageCollectorSettings) {
71
+ is MemoryEagerGcSettings -> MemoryGarbageCollectorSettings .Eager
72
+ is MemoryLruGcSettings -> MemoryGarbageCollectorSettings .LRUGC (settings.sizeBytes)
73
+ else -> throw IllegalArgumentException (" Existing settings does not have valid GarbageCollectionSettings" )
74
+ }
75
+ LocalCacheSettings .Memory (garbageCollectionSettings)
76
+ }
77
+
78
+ is PersistentCacheSettings -> LocalCacheSettings .Persistent (localCacheSettings.sizeBytes)
79
+ else -> throw IllegalArgumentException (" Existing settings is not of a valid type" )
80
+ }
81
+ } ? : kotlin.run {
82
+ @Suppress(" DEPRECATION" )
83
+ when {
84
+ isPersistenceEnabled -> LocalCacheSettings .Persistent (cacheSizeBytes)
85
+ cacheSizeBytes == FirebaseFirestoreSettings .CACHE_SIZE_UNLIMITED -> LocalCacheSettings .Memory (MemoryGarbageCollectorSettings .Eager )
86
+ else -> LocalCacheSettings .Memory (MemoryGarbageCollectorSettings .LRUGC (cacheSizeBytes))
87
+ }
88
+ },
89
+ callbackExecutorMap[native] ? : TaskExecutors .MAIN_THREAD
90
+ )
91
+ }
92
+ set(value) {
93
+ native.firestoreSettings = androidFirestoreSettings {
94
+ isSslEnabled = value.sslEnabled
95
+ host = value.host
96
+ setLocalCacheSettings(value.cacheSettings.android)
97
+ }
98
+ callbackExecutorMap[native] = value.callbackExecutor
99
+ }
29
100
30
- actual fun collection (collectionPath : String ) = CollectionReference ( NativeCollectionReference (android .collection(collectionPath) ))
101
+ actual fun collection (collectionPath : String ) = NativeCollectionReference (native .collection(collectionPath))
31
102
32
- actual fun collectionGroup (collectionId : String ) = Query (android .collectionGroup(collectionId).native)
103
+ actual fun collectionGroup (collectionId : String ) = native .collectionGroup(collectionId).native
33
104
34
- actual fun document (documentPath : String ) = DocumentReference ( NativeDocumentReference (android .document(documentPath) ))
105
+ actual fun document (documentPath : String ) = NativeDocumentReference (native .document(documentPath))
35
106
36
- actual fun batch () = WriteBatch ( NativeWriteBatch (android .batch() ))
107
+ actual fun batch () = NativeWriteBatch (native .batch())
37
108
38
109
actual fun setLoggingEnabled (loggingEnabled : Boolean ) =
39
110
com.google.firebase.firestore.FirebaseFirestore .setLoggingEnabled(loggingEnabled)
40
111
41
- actual suspend fun <T > runTransaction (func : suspend Transaction .() -> T ): T =
42
- android .runTransaction { runBlocking { Transaction ( NativeTransaction (it) ).func() } }.await()
112
+ actual suspend fun <T > runTransaction (func : suspend NativeTransaction .() -> T ): T =
113
+ native .runTransaction { runBlocking { NativeTransaction (it).func() } }.await()
43
114
44
115
actual suspend fun clearPersistence () =
45
- android .clearPersistence().await().run { }
116
+ native .clearPersistence().await().run { }
46
117
47
118
actual fun useEmulator (host : String , port : Int ) {
48
- android.useEmulator(host, port)
49
- android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings .Builder ()
50
- .setPersistenceEnabled(false )
51
- .build()
52
- }
53
-
54
- actual fun setSettings (persistenceEnabled : Boolean? , sslEnabled : Boolean? , host : String? , cacheSizeBytes : Long? ) {
55
- android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings .Builder ().also { builder ->
56
- persistenceEnabled?.let { builder.setPersistenceEnabled(it) }
57
- sslEnabled?.let { builder.isSslEnabled = it }
58
- host?.let { builder.host = it }
59
- cacheSizeBytes?.let { builder.cacheSizeBytes = it }
60
- }.build()
119
+ native.useEmulator(host, port)
61
120
}
62
121
63
122
actual suspend fun disableNetwork () =
64
- android .disableNetwork().await().run { }
123
+ native .disableNetwork().await().run { }
65
124
66
125
actual suspend fun enableNetwork () =
67
- android .enableNetwork().await().run { }
126
+ native .enableNetwork().await().run { }
68
127
69
128
}
70
129
130
+ val FirebaseFirestore .android get() = native
131
+
132
+ actual data class FirebaseFirestoreSettings (
133
+ actual val sslEnabled : Boolean ,
134
+ actual val host : String ,
135
+ actual val cacheSettings : LocalCacheSettings ,
136
+ val callbackExecutor : Executor ,
137
+ ) {
138
+
139
+ actual companion object {
140
+ actual val CACHE_SIZE_UNLIMITED : Long = - 1L
141
+ internal actual val DEFAULT_HOST : String = " firestore.googleapis.com"
142
+ internal actual val MINIMUM_CACHE_BYTES : Long = 1 * 1024 * 1024
143
+ internal actual val DEFAULT_CACHE_SIZE_BYTES : Long = 100 * 1024 * 1024
144
+ }
145
+
146
+ actual class Builder internal constructor(
147
+ actual var sslEnabled : Boolean ,
148
+ actual var host : String ,
149
+ actual var cacheSettings : LocalCacheSettings ,
150
+ var callbackExecutor : Executor ,
151
+ ) {
152
+
153
+ actual constructor () : this (
154
+ true ,
155
+ DEFAULT_HOST ,
156
+ persistentCacheSettings { },
157
+ TaskExecutors .MAIN_THREAD
158
+ )
159
+ actual constructor (settings: FirebaseFirestoreSettings ) : this (settings.sslEnabled, settings.host, settings.cacheSettings, settings.callbackExecutor)
160
+
161
+ actual fun build (): FirebaseFirestoreSettings = FirebaseFirestoreSettings (sslEnabled, host, cacheSettings, callbackExecutor)
162
+ }
163
+ }
164
+
165
+ actual fun firestoreSettings (
166
+ settings : FirebaseFirestoreSettings ? ,
167
+ builder : FirebaseFirestoreSettings .Builder .() -> Unit
168
+ ): FirebaseFirestoreSettings = FirebaseFirestoreSettings .Builder ().apply {
169
+ settings?.let {
170
+ sslEnabled = it.sslEnabled
171
+ host = it.host
172
+ cacheSettings = it.cacheSettings
173
+ callbackExecutor = it.callbackExecutor
174
+ }
175
+ }.apply (builder).build()
176
+
71
177
internal val SetOptions .android: com.google.firebase.firestore.SetOptions ? get() = when (this ) {
72
178
is SetOptions .Merge -> com.google.firebase.firestore.SetOptions .merge()
73
179
is SetOptions .Overwrite -> null
@@ -206,19 +312,27 @@ internal actual class NativeDocumentReference actual constructor(actual val nati
206
312
207
313
actual val snapshots: Flow <NativeDocumentSnapshot > get() = snapshots()
208
314
209
- actual fun snapshots (includeMetadataChanges : Boolean ) = callbackFlow {
210
- val metadataChanges = if (includeMetadataChanges) MetadataChanges .INCLUDE else MetadataChanges .EXCLUDE
211
- val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception ->
212
- snapshot?.let { trySend(NativeDocumentSnapshot (snapshot)) }
213
- exception?.let { close(exception) }
214
- }
215
- awaitClose { listener.remove() }
315
+ actual fun snapshots (includeMetadataChanges : Boolean ) = addSnapshotListener(includeMetadataChanges) { snapshot, exception ->
316
+ snapshot?.let { trySend(NativeDocumentSnapshot (snapshot)) }
317
+ exception?.let { close(exception) }
216
318
}
217
319
218
320
override fun equals (other : Any? ): Boolean =
219
321
this == = other || other is NativeDocumentReference && nativeValue == other.nativeValue
220
322
override fun hashCode (): Int = nativeValue.hashCode()
221
323
override fun toString (): String = nativeValue.toString()
324
+
325
+ private fun addSnapshotListener (
326
+ includeMetadataChanges : Boolean = false,
327
+ listener : ProducerScope <NativeDocumentSnapshot >.(com.google.firebase.firestore.DocumentSnapshot ? , com.google.firebase.firestore.FirebaseFirestoreException ? ) -> Unit
328
+ ) = callbackFlow {
329
+ val executor = callbackExecutorMap[android.firestore] ? : TaskExecutors .MAIN_THREAD
330
+ val metadataChanges = if (includeMetadataChanges) MetadataChanges .INCLUDE else MetadataChanges .EXCLUDE
331
+ val registration = android.addSnapshotListener(executor, metadataChanges) { snapshots, exception ->
332
+ listener(snapshots, exception)
333
+ }
334
+ awaitClose { registration.remove() }
335
+ }
222
336
}
223
337
224
338
val DocumentReference .android get() = native.android
@@ -235,21 +349,14 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) {
235
349
236
350
actual fun limit (limit : Number ) = Query (NativeQuery (android.limit(limit.toLong())))
237
351
238
- actual val snapshots get() = callbackFlow<QuerySnapshot > {
239
- val listener = android.addSnapshotListener { snapshot, exception ->
240
- snapshot?.let { trySend(QuerySnapshot (snapshot)) }
241
- exception?.let { close(exception) }
242
- }
243
- awaitClose { listener.remove() }
352
+ actual val snapshots get() = addSnapshotListener { snapshot, exception ->
353
+ snapshot?.let { trySend(QuerySnapshot (snapshot)) }
354
+ exception?.let { close(exception) }
244
355
}
245
356
246
- actual fun snapshots (includeMetadataChanges : Boolean ) = callbackFlow<QuerySnapshot > {
247
- val metadataChanges = if (includeMetadataChanges) MetadataChanges .INCLUDE else MetadataChanges .EXCLUDE
248
- val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception ->
249
- snapshot?.let { trySend(QuerySnapshot (snapshot)) }
250
- exception?.let { close(exception) }
251
- }
252
- awaitClose { listener.remove() }
357
+ actual fun snapshots (includeMetadataChanges : Boolean ) = addSnapshotListener(includeMetadataChanges) { snapshot, exception ->
358
+ snapshot?.let { trySend(QuerySnapshot (snapshot)) }
359
+ exception?.let { close(exception) }
253
360
}
254
361
255
362
internal actual fun where (filter : Filter ) = Query (
@@ -331,6 +438,18 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) {
331
438
internal actual fun _endBefore (vararg fieldValues : Any ) = Query (android.endBefore(* fieldValues).native)
332
439
internal actual fun _endAt (document : DocumentSnapshot ) = Query (android.endAt(document.android).native)
333
440
internal actual fun _endAt (vararg fieldValues : Any ) = Query (android.endAt(* fieldValues).native)
441
+
442
+ private fun addSnapshotListener (
443
+ includeMetadataChanges : Boolean = false,
444
+ listener : ProducerScope <QuerySnapshot >.(com.google.firebase.firestore.QuerySnapshot ? , com.google.firebase.firestore.FirebaseFirestoreException ? ) -> Unit
445
+ ) = callbackFlow {
446
+ val executor = callbackExecutorMap[android.firestore] ? : TaskExecutors .MAIN_THREAD
447
+ val metadataChanges = if (includeMetadataChanges) MetadataChanges .INCLUDE else MetadataChanges .EXCLUDE
448
+ val registration = android.addSnapshotListener(executor, metadataChanges) { snapshots, exception ->
449
+ listener(snapshots, exception)
450
+ }
451
+ awaitClose { registration.remove() }
452
+ }
334
453
}
335
454
336
455
actual typealias Direction = com.google.firebase.firestore.Query .Direction
0 commit comments