Skip to content

Commit

Permalink
Added Settings to deal with newer settings & add callback threads
Browse files Browse the repository at this point in the history
  • Loading branch information
Daeda88 committed Dec 20, 2023
1 parent eb56044 commit c80c5f2
Show file tree
Hide file tree
Showing 16 changed files with 838 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ package dev.gitlive.firebase.auth
actual val emulatorHost: String = "10.0.2.2"

actual val context: Any = Unit

@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
actual annotation class IgnoreForAndroidUnitTest
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
* Copyright (c) 2020 GitLive Ltd. Use of this source code is governed by the Apache 2.0 license.
*/

@file:JvmName("databaseAndroid")
package dev.gitlive.firebase.database

import com.google.android.gms.tasks.Task
import com.google.firebase.database.*
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.FirebaseApp
import dev.gitlive.firebase.database.ChildEvent.Type
import dev.gitlive.firebase.database.FirebaseDatabase.Companion.FirebaseDatabase
import dev.gitlive.firebase.decode
import dev.gitlive.firebase.encode
import kotlinx.coroutines.CompletableDeferred
Expand Down Expand Up @@ -37,27 +37,37 @@ suspend fun <T> Task<T>.awaitWhileOnline(): T =


actual val Firebase.database
by lazy { FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance()) }
by lazy { FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance()) }

actual fun Firebase.database(url: String) =
FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance(url))
FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(url))

actual fun Firebase.database(app: FirebaseApp) =
FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance(app.android))
FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(app.android))

actual fun Firebase.database(app: FirebaseApp, url: String) =
FirebaseDatabase(com.google.firebase.database.FirebaseDatabase.getInstance(app.android, url))
FirebaseDatabase.getInstance(com.google.firebase.database.FirebaseDatabase.getInstance(app.android, url))

actual class FirebaseDatabase private constructor(val android: com.google.firebase.database.FirebaseDatabase) {
actual class FirebaseDatabase internal constructor(val android: com.google.firebase.database.FirebaseDatabase) {

companion object {
private val instances = WeakHashMap<com.google.firebase.database.FirebaseDatabase, FirebaseDatabase>()

internal fun FirebaseDatabase(
internal fun getInstance(
android: com.google.firebase.database.FirebaseDatabase
) = instances.getOrPut(android) { dev.gitlive.firebase.database.FirebaseDatabase(android) }
}

actual data class Settings(
actual val persistenceEnabled: Boolean = false,
actual val persistenceCacheSizeBytes: Long? = null,
) {

actual companion object {
actual fun createSettings(persistenceEnabled: Boolean, persistenceCacheSizeBytes: Long?) = Settings(persistenceEnabled, persistenceCacheSizeBytes)
}
}

private var persistenceEnabled = true

actual fun reference(path: String) =
Expand All @@ -66,8 +76,11 @@ actual class FirebaseDatabase private constructor(val android: com.google.fireba
actual fun reference() =
DatabaseReference(android.reference, persistenceEnabled)

actual fun setPersistenceEnabled(enabled: Boolean) =
android.setPersistenceEnabled(enabled).also { persistenceEnabled = enabled }
actual fun setSettings(settings: Settings) {
android.setPersistenceEnabled(settings.persistenceEnabled)
persistenceEnabled = settings.persistenceEnabled
settings.persistenceCacheSizeBytes?.let { android.setPersistenceCacheSizeBytes(it) }
}

actual fun setLoggingEnabled(enabled: Boolean) =
android.setLogLevel(Logger.Level.DEBUG.takeIf { enabled } ?: Logger.Level.NONE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,25 @@ expect fun Firebase.database(app: FirebaseApp): FirebaseDatabase
expect fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase

expect class FirebaseDatabase {

class Settings {
val persistenceEnabled: Boolean
val persistenceCacheSizeBytes: Long?

companion object {
fun createSettings(persistenceEnabled: Boolean = false, persistenceCacheSizeBytes: Long? = null): Settings
}
}

fun reference(path: String): DatabaseReference
fun reference(): DatabaseReference
fun setPersistenceEnabled(enabled: Boolean)
fun setSettings(settings: Settings)
fun setLoggingEnabled(enabled: Boolean)
fun useEmulator(host: String, port: Int)
}

fun FirebaseDatabase.setPersistenceEnabled(enabled: Boolean) = setSettings(FirebaseDatabase.Settings.createSettings(persistenceEnabled = enabled))

data class ChildEvent internal constructor(
val snapshot: DataSnapshot,
val type: Type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerializationStrategy
import platform.Foundation.NSError
import platform.Foundation.allObjects
import platform.darwin.dispatch_queue_t
import kotlin.collections.component1
import kotlin.collections.component2

Expand All @@ -44,14 +45,27 @@ actual fun Firebase.database(app: FirebaseApp, url: String): FirebaseDatabase =

actual class FirebaseDatabase internal constructor(val ios: FIRDatabase) {

actual data class Settings(
actual val persistenceEnabled: Boolean = false,
actual val persistenceCacheSizeBytes: Long? = null,
val callbackQueue: dispatch_queue_t = null
) {

actual companion object {
actual fun createSettings(persistenceEnabled: Boolean, persistenceCacheSizeBytes: Long?) = Settings(persistenceEnabled, persistenceCacheSizeBytes)
}
}

actual fun reference(path: String) =
DatabaseReference(ios.referenceWithPath(path), ios.persistenceEnabled)

actual fun reference() =
DatabaseReference(ios.reference(), ios.persistenceEnabled)

actual fun setPersistenceEnabled(enabled: Boolean) {
ios.persistenceEnabled = enabled
actual fun setSettings(settings: Settings) {
ios.persistenceEnabled = settings.persistenceEnabled
settings.persistenceCacheSizeBytes?.let { ios.setPersistenceCacheSizeBytes(it.toULong()) }
settings.callbackQueue?.let { ios.callbackQueue = it }
}

actual fun setLoggingEnabled(enabled: Boolean) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,20 @@ actual fun Firebase.database(app: FirebaseApp, url: String) =
rethrow { FirebaseDatabase(getDatabase(app = app.js, url = url)) }

actual class FirebaseDatabase internal constructor(val js: Database) {

actual data class Settings(
actual val persistenceEnabled: Boolean = false,
actual val persistenceCacheSizeBytes: Long? = null,
) {

actual companion object {
actual fun createSettings(persistenceEnabled: Boolean, persistenceCacheSizeBytes: Long?) = Settings(persistenceEnabled, persistenceCacheSizeBytes)
}
}

actual fun reference(path: String) = rethrow { DatabaseReference(ref(js, path), js) }
actual fun reference() = rethrow { DatabaseReference(ref(js), js) }
actual fun setPersistenceEnabled(enabled: Boolean) {}
actual fun setSettings(settings: Settings) {}
actual fun setLoggingEnabled(enabled: Boolean) = rethrow { enableLogging(enabled) }
actual fun useEmulator(host: String, port: Int) = rethrow { connectDatabaseEmulator(js, host, port) }
}
Expand Down
4 changes: 0 additions & 4 deletions firebase-firestore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,6 @@ kotlin {
api("com.google.firebase:firebase-firestore")
}
}

getByName("jvmMain") {
kotlin.srcDir("src/androidMain/kotlin")
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
@file:JvmName("android")
package dev.gitlive.firebase.firestore

import com.google.android.gms.tasks.Task
import com.google.android.gms.tasks.TaskExecutors
import com.google.firebase.firestore.*
import com.google.firebase.firestore.util.Executors
import dev.gitlive.firebase.*
import dev.gitlive.firebase.firestore.FirebaseFirestoreException
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
Expand All @@ -16,6 +19,8 @@ import kotlinx.coroutines.tasks.await
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationStrategy
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor

actual val Firebase.firestore get() =
FirebaseFirestore(com.google.firebase.firestore.FirebaseFirestore.getInstance())
Expand All @@ -37,8 +42,40 @@ private fun <R> performUpdate(
update: (com.google.firebase.firestore.FieldPath, Any?, Array<Any?>) -> R
) = performUpdate(fieldsAndValues, { it.android }, { encode(it, true) }, update)

val LocalCacheSettings.android: com.google.firebase.firestore.LocalCacheSettings get() = when (this) {
is LocalCacheSettings.Persistent -> persistentCacheSettings {
sizeBytes?.let { setSizeBytes(it) }
}
is LocalCacheSettings.Memory -> memoryCacheSettings {
setGcSettings(
when (garbaseCollectorSettings) {
is LocalCacheSettings.Memory.GarbageCollectorSettings.Eager -> memoryEagerGcSettings { }
is LocalCacheSettings.Memory.GarbageCollectorSettings.LRUGC -> memoryLruGcSettings {
garbaseCollectorSettings.sizeBytes?.let {
setSizeBytes(it)
}
}
}
)
}
}

// Since on iOS Callback threads are set as settings, we store the settings explicitly here as well
private val callbackExecutorMap = ConcurrentHashMap<com.google.firebase.firestore.FirebaseFirestore, Executor>()

actual class FirebaseFirestore(val android: com.google.firebase.firestore.FirebaseFirestore) {

actual data class Settings(
actual val sslEnabled: Boolean? = null,
actual val host: String? = null,
actual val cacheSettings: LocalCacheSettings? = null,
val callbackExecutor: Executor = TaskExecutors.MAIN_THREAD,
) {
actual companion object {
actual fun create(sslEnabled: Boolean?, host: String?, cacheSettings: LocalCacheSettings?) = Settings(sslEnabled, host, cacheSettings)
}
}

actual fun collection(collectionPath: String) = CollectionReference(android.collection(collectionPath))

actual fun collectionGroup(collectionId: String) = Query(android.collectionGroup(collectionId))
Expand All @@ -58,19 +95,33 @@ actual class FirebaseFirestore(val android: com.google.firebase.firestore.Fireba

actual fun useEmulator(host: String, port: Int) {
android.useEmulator(host, port)
android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings.Builder()
.setPersistenceEnabled(false)
.build()
android.firestoreSettings = firestoreSettings { }
}

actual fun setSettings(persistenceEnabled: Boolean?, sslEnabled: Boolean?, host: String?, cacheSizeBytes: Long?) {
android.firestoreSettings = com.google.firebase.firestore.FirebaseFirestoreSettings.Builder().also { builder ->
persistenceEnabled?.let { builder.setPersistenceEnabled(it) }
sslEnabled?.let { builder.isSslEnabled = it }
host?.let { builder.host = it }
cacheSizeBytes?.let { builder.cacheSizeBytes = it }
}.build()
actual fun setSettings(settings: Settings) {
android.firestoreSettings = firestoreSettings {
settings.sslEnabled?.let { isSslEnabled = it }
settings.host?.let { host = it }
settings.cacheSettings?.let { setLocalCacheSettings(it.android) }
}
callbackExecutorMap[android] = settings.callbackExecutor
}

@Suppress("DEPRECATION")
actual fun updateSettings(settings: Settings) {
android.firestoreSettings = firestoreSettings {
isSslEnabled = settings.sslEnabled ?: android.firestoreSettings.isSslEnabled
host = settings.host ?: android.firestoreSettings.host
val cacheSettings = settings.cacheSettings?.android ?: android.firestoreSettings.cacheSettings
cacheSettings?.let {
setLocalCacheSettings(it)
} ?: kotlin.run {
isPersistenceEnabled = android.firestoreSettings.isPersistenceEnabled
setCacheSizeBytes(android.firestoreSettings.cacheSizeBytes)
}
}
callbackExecutorMap[android] = settings.callbackExecutor
}

actual suspend fun disableNetwork() =
android.disableNetwork().await().run { }
Expand Down Expand Up @@ -269,18 +320,26 @@ actual class DocumentReference actual constructor(internal actual val nativeValu

actual val snapshots: Flow<DocumentSnapshot> get() = snapshots()

actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow {
val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE
val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception ->
snapshot?.let { trySend(DocumentSnapshot(snapshot)) }
exception?.let { close(exception) }
}
awaitClose { listener.remove() }
actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception ->
snapshot?.let { trySend(DocumentSnapshot(snapshot)) }
exception?.let { close(exception) }
}
override fun equals(other: Any?): Boolean =
this === other || other is DocumentReference && nativeValue == other.nativeValue
override fun hashCode(): Int = nativeValue.hashCode()
override fun toString(): String = nativeValue.toString()

private fun addSnapshotListener(
includeMetadataChanges: Boolean = false,
listener: ProducerScope<DocumentSnapshot>.(com.google.firebase.firestore.DocumentSnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit
) = callbackFlow {
val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD
val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE
val registration = android.addSnapshotListener(executor, metadataChanges) { snapshots, exception ->
listener(snapshots, exception)
}
awaitClose { registration.remove() }
}
}

actual open class Query(open val android: com.google.firebase.firestore.Query) {
Expand All @@ -289,21 +348,14 @@ actual open class Query(open val android: com.google.firebase.firestore.Query) {

actual fun limit(limit: Number) = Query(android.limit(limit.toLong()))

actual val snapshots get() = callbackFlow<QuerySnapshot> {
val listener = android.addSnapshotListener { snapshot, exception ->
snapshot?.let { trySend(QuerySnapshot(snapshot)) }
exception?.let { close(exception) }
}
awaitClose { listener.remove() }
actual val snapshots get() = addSnapshotListener { snapshot, exception ->
snapshot?.let { trySend(QuerySnapshot(snapshot)) }
exception?.let { close(exception) }
}

actual fun snapshots(includeMetadataChanges: Boolean) = callbackFlow<QuerySnapshot> {
val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE
val listener = android.addSnapshotListener(metadataChanges) { snapshot, exception ->
snapshot?.let { trySend(QuerySnapshot(snapshot)) }
exception?.let { close(exception) }
}
awaitClose { listener.remove() }
actual fun snapshots(includeMetadataChanges: Boolean) = addSnapshotListener(includeMetadataChanges) { snapshot, exception ->
snapshot?.let { trySend(QuerySnapshot(snapshot)) }
exception?.let { close(exception) }
}

internal actual fun _where(field: String, equalTo: Any?) = Query(android.whereEqualTo(field, equalTo))
Expand Down Expand Up @@ -352,6 +404,18 @@ actual open class Query(open val android: com.google.firebase.firestore.Query) {
internal actual fun _endBefore(vararg fieldValues: Any) = Query(android.endBefore(*fieldValues))
internal actual fun _endAt(document: DocumentSnapshot) = Query(android.endAt(document.android))
internal actual fun _endAt(vararg fieldValues: Any) = Query(android.endAt(*fieldValues))

private fun addSnapshotListener(
includeMetadataChanges: Boolean = false,
listener: ProducerScope<QuerySnapshot>.(com.google.firebase.firestore.QuerySnapshot?, com.google.firebase.firestore.FirebaseFirestoreException?) -> Unit
) = callbackFlow {
val executor = callbackExecutorMap[android.firestore] ?: TaskExecutors.MAIN_THREAD
val metadataChanges = if(includeMetadataChanges) MetadataChanges.INCLUDE else MetadataChanges.EXCLUDE
val registration = android.addSnapshotListener(executor, metadataChanges) { snapshots, exception ->
listener(snapshots, exception)
}
awaitClose { registration.remove() }
}
}

actual typealias Direction = com.google.firebase.firestore.Query.Direction
Expand Down
Loading

0 comments on commit c80c5f2

Please sign in to comment.