55@file:JvmName(" android" )
66package dev.gitlive.firebase.firestore
77
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
812import com.google.firebase.firestore.MetadataChanges
13+ import com.google.firebase.firestore.PersistentCacheSettings
914import dev.gitlive.firebase.Firebase
1015import dev.gitlive.firebase.FirebaseApp
16+ import kotlinx.coroutines.channels.ProducerScope
1117import dev.gitlive.firebase.firestore.Source.*
1218import kotlinx.coroutines.channels.awaitClose
1319import kotlinx.coroutines.flow.Flow
1420import kotlinx.coroutines.flow.callbackFlow
1521import kotlinx.coroutines.runBlocking
1622import kotlinx.coroutines.tasks.await
17- import kotlinx.serialization.Serializable
23+ import java.util.concurrent.ConcurrentHashMap
24+ import java.util.concurrent.Executor
1825import com.google.firebase.firestore.FieldPath as AndroidFieldPath
1926import com.google.firebase.firestore.Filter as AndroidFilter
2027import 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
2133
2234actual val Firebase .firestore get() =
2335 FirebaseFirestore (com.google.firebase.firestore.FirebaseFirestore .getInstance())
2436
2537actual fun Firebase.firestore (app : FirebaseApp ) =
2638 FirebaseFirestore (com.google.firebase.firestore.FirebaseFirestore .getInstance(app.android))
2739
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+ }
29100
30- actual fun collection (collectionPath : String ) = CollectionReference ( NativeCollectionReference (android .collection(collectionPath) ))
101+ actual fun collection (collectionPath : String ) = NativeCollectionReference (native .collection(collectionPath))
31102
32- actual fun collectionGroup (collectionId : String ) = Query (android .collectionGroup(collectionId).native)
103+ actual fun collectionGroup (collectionId : String ) = native .collectionGroup(collectionId).native
33104
34- actual fun document (documentPath : String ) = DocumentReference ( NativeDocumentReference (android .document(documentPath) ))
105+ actual fun document (documentPath : String ) = NativeDocumentReference (native .document(documentPath))
35106
36- actual fun batch () = WriteBatch ( NativeWriteBatch (android .batch() ))
107+ actual fun batch () = NativeWriteBatch (native .batch())
37108
38109 actual fun setLoggingEnabled (loggingEnabled : Boolean ) =
39110 com.google.firebase.firestore.FirebaseFirestore .setLoggingEnabled(loggingEnabled)
40111
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()
43114
44115 actual suspend fun clearPersistence () =
45- android .clearPersistence().await().run { }
116+ native .clearPersistence().await().run { }
46117
47118 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)
61120 }
62121
63122 actual suspend fun disableNetwork () =
64- android .disableNetwork().await().run { }
123+ native .disableNetwork().await().run { }
65124
66125 actual suspend fun enableNetwork () =
67- android .enableNetwork().await().run { }
126+ native .enableNetwork().await().run { }
68127
69128}
70129
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+
71177internal val SetOptions .android: com.google.firebase.firestore.SetOptions ? get() = when (this ) {
72178 is SetOptions .Merge -> com.google.firebase.firestore.SetOptions .merge()
73179 is SetOptions .Overwrite -> null
@@ -206,19 +312,27 @@ internal actual class NativeDocumentReference actual constructor(actual val nati
206312
207313 actual val snapshots: Flow <NativeDocumentSnapshot > get() = snapshots()
208314
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) }
216318 }
217319
218320 override fun equals (other : Any? ): Boolean =
219321 this == = other || other is NativeDocumentReference && nativeValue == other.nativeValue
220322 override fun hashCode (): Int = nativeValue.hashCode()
221323 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+ }
222336}
223337
224338val DocumentReference .android get() = native.android
@@ -235,21 +349,14 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) {
235349
236350 actual fun limit (limit : Number ) = Query (NativeQuery (android.limit(limit.toLong())))
237351
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) }
244355 }
245356
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) }
253360 }
254361
255362 internal actual fun where (filter : Filter ) = Query (
@@ -331,6 +438,18 @@ actual open class Query internal actual constructor(nativeQuery: NativeQuery) {
331438 internal actual fun _endBefore (vararg fieldValues : Any ) = Query (android.endBefore(* fieldValues).native)
332439 internal actual fun _endAt (document : DocumentSnapshot ) = Query (android.endAt(document.android).native)
333440 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+ }
334453}
335454
336455actual typealias Direction = com.google.firebase.firestore.Query .Direction
0 commit comments