Skip to content

Commit 101869c

Browse files
authored
Merge pull request GitLiveApp#562 from splendo/feature/552-ios-settings-crash
Fixed a crash for settings when iOS would access the same Firebase instance twice
2 parents 5b0c17b + c987bea commit 101869c

File tree

8 files changed

+75
-103
lines changed

8 files changed

+75
-103
lines changed

firebase-firestore/src/androidMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt

Lines changed: 9 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
package dev.gitlive.firebase.firestore.internal
22

3-
import com.google.android.gms.tasks.TaskExecutors
4-
import com.google.firebase.firestore.MemoryCacheSettings
5-
import com.google.firebase.firestore.MemoryEagerGcSettings
6-
import com.google.firebase.firestore.MemoryLruGcSettings
7-
import com.google.firebase.firestore.PersistentCacheSettings
83
import com.google.firebase.firestore.firestoreSettings
94
import dev.gitlive.firebase.firestore.FirebaseFirestoreSettings
10-
import dev.gitlive.firebase.firestore.LocalCacheSettings
11-
import dev.gitlive.firebase.firestore.MemoryGarbageCollectorSettings
125
import dev.gitlive.firebase.firestore.NativeFirebaseFirestore
136
import dev.gitlive.firebase.firestore.NativeTransaction
147
import dev.gitlive.firebase.firestore.android
@@ -17,59 +10,6 @@ import kotlinx.coroutines.tasks.await
1710

1811
internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual val native: NativeFirebaseFirestore) {
1912

20-
actual var settings: FirebaseFirestoreSettings
21-
get() = with(native.firestoreSettings) {
22-
FirebaseFirestoreSettings(
23-
isSslEnabled,
24-
host,
25-
cacheSettings?.let { localCacheSettings ->
26-
when (localCacheSettings) {
27-
is MemoryCacheSettings -> {
28-
val garbageCollectionSettings =
29-
when (val settings = localCacheSettings.garbageCollectorSettings) {
30-
is MemoryEagerGcSettings -> MemoryGarbageCollectorSettings.Eager
31-
is MemoryLruGcSettings -> MemoryGarbageCollectorSettings.LRUGC(
32-
settings.sizeBytes,
33-
)
34-
35-
else -> throw IllegalArgumentException("Existing settings does not have valid GarbageCollectionSettings")
36-
}
37-
LocalCacheSettings.Memory(garbageCollectionSettings)
38-
}
39-
40-
is PersistentCacheSettings -> LocalCacheSettings.Persistent(
41-
localCacheSettings.sizeBytes,
42-
)
43-
44-
else -> throw IllegalArgumentException("Existing settings is not of a valid type")
45-
}
46-
} ?: kotlin.run {
47-
@Suppress("DEPRECATION")
48-
when {
49-
isPersistenceEnabled -> LocalCacheSettings.Persistent(cacheSizeBytes)
50-
cacheSizeBytes == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED -> LocalCacheSettings.Memory(
51-
MemoryGarbageCollectorSettings.Eager,
52-
)
53-
54-
else -> LocalCacheSettings.Memory(
55-
MemoryGarbageCollectorSettings.LRUGC(
56-
cacheSizeBytes,
57-
),
58-
)
59-
}
60-
},
61-
callbackExecutorMap[native] ?: TaskExecutors.MAIN_THREAD,
62-
)
63-
}
64-
set(value) {
65-
native.firestoreSettings = firestoreSettings {
66-
isSslEnabled = value.sslEnabled
67-
host = value.host
68-
setLocalCacheSettings(value.cacheSettings.android)
69-
}
70-
callbackExecutorMap[native] = value.callbackExecutor
71-
}
72-
7313
actual fun collection(collectionPath: String) = native.collection(collectionPath)
7414

7515
actual fun collectionGroup(collectionId: String) = native.collectionGroup(collectionId)
@@ -82,6 +22,15 @@ internal actual class NativeFirebaseFirestoreWrapper actual constructor(actual v
8222
actual fun setLoggingEnabled(loggingEnabled: Boolean) =
8323
com.google.firebase.firestore.FirebaseFirestore.setLoggingEnabled(loggingEnabled)
8424

25+
actual fun applySettings(settings: FirebaseFirestoreSettings) {
26+
native.firestoreSettings = firestoreSettings {
27+
isSslEnabled = settings.sslEnabled
28+
host = settings.host
29+
setLocalCacheSettings(settings.cacheSettings.android)
30+
}
31+
callbackExecutorMap[native] = settings.callbackExecutor
32+
}
33+
8534
actual suspend fun <T> runTransaction(func: suspend NativeTransaction.() -> T): T =
8635
native.runTransaction { runBlocking { it.func() } }.await()
8736

firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ public class FirebaseFirestore internal constructor(private val wrapper: NativeF
4444

4545
// Important to leave this as a get property since on JS it is initialized lazily
4646
internal val native: NativeFirebaseFirestore get() = wrapper.native
47+
4748
public var settings: FirebaseFirestoreSettings
48-
get() = wrapper.settings
49+
@Deprecated("Property can only be written.", level = DeprecationLevel.ERROR)
50+
get() = throw NotImplementedError()
4951
set(value) {
50-
wrapper.settings = value
52+
wrapper.applySettings(value)
5153
}
5254

5355
public fun collection(collectionPath: String): CollectionReference = CollectionReference(wrapper.collection(collectionPath))
@@ -65,7 +67,7 @@ public class FirebaseFirestore internal constructor(private val wrapper: NativeF
6567
wrapper.useEmulator(host, port)
6668
}
6769

68-
@Deprecated("Use settings instead", replaceWith = ReplaceWith("settings = firestoreSettings{}"))
70+
@Deprecated("Use SettingsBuilder instead", replaceWith = ReplaceWith("settings = firestoreSettings { }", "dev.gitlive.firebase.firestore.firestoreSettings"))
6971
public fun setSettings(
7072
persistenceEnabled: Boolean? = null,
7173
sslEnabled: Boolean? = null,
@@ -76,18 +78,22 @@ public class FirebaseFirestore internal constructor(private val wrapper: NativeF
7678
this.sslEnabled = sslEnabled ?: true
7779
this.host = host ?: FirebaseFirestoreSettings.DEFAULT_HOST
7880
this.cacheSettings = if (persistenceEnabled != false) {
79-
LocalCacheSettings.Persistent(cacheSizeBytes ?: FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED)
81+
LocalCacheSettings.Persistent(
82+
cacheSizeBytes ?: FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED,
83+
)
8084
} else {
8185
val cacheSize = cacheSizeBytes ?: FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED
82-
val garbageCollectionSettings = if (cacheSize == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED) {
83-
MemoryGarbageCollectorSettings.Eager
84-
} else {
85-
MemoryGarbageCollectorSettings.LRUGC(cacheSize)
86-
}
86+
val garbageCollectionSettings =
87+
if (cacheSize == FirebaseFirestoreSettings.CACHE_SIZE_UNLIMITED) {
88+
MemoryGarbageCollectorSettings.Eager
89+
} else {
90+
MemoryGarbageCollectorSettings.LRUGC(cacheSize)
91+
}
8792
LocalCacheSettings.Memory(garbageCollectionSettings)
8893
}
8994
}
9095
}
96+
9197
public suspend fun disableNetwork() {
9298
wrapper.disableNetwork()
9399
}

firebase-firestore/src/commonMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import dev.gitlive.firebase.firestore.NativeWriteBatch
99

1010
internal expect class NativeFirebaseFirestoreWrapper internal constructor(native: NativeFirebaseFirestore) {
1111
val native: NativeFirebaseFirestore
12-
var settings: FirebaseFirestoreSettings
1312

1413
fun collection(collectionPath: String): NativeCollectionReference
1514
fun collectionGroup(collectionId: String): NativeQuery
1615
fun document(documentPath: String): NativeDocumentReference
1716
fun batch(): NativeWriteBatch
1817
fun setLoggingEnabled(loggingEnabled: Boolean)
18+
fun applySettings(settings: FirebaseFirestoreSettings)
1919
suspend fun clearPersistence()
2020
suspend fun <T> runTransaction(func: suspend NativeTransaction.() -> T): T
2121
fun useEmulator(host: String, port: Int)

firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/FirestoreSourceTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ class FirestoreSourceTest {
3939
)
4040

4141
firestore = Firebase.firestore(app).apply {
42-
useEmulator(emulatorHost, 8080)
43-
settings = firestoreSettings(settings) {
42+
settings = firestoreSettings {
4443
cacheSettings = if (persistenceEnabled) {
4544
persistentCacheSettings { }
4645
} else {
4746
memoryCacheSettings { }
4847
}
4948
}
49+
useEmulator(emulatorHost, 8080)
5050
}
5151
}
5252

firebase-firestore/src/commonTest/kotlin/dev/gitlive/firebase/firestore/firestore.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package dev.gitlive.firebase.firestore
66

77
import dev.gitlive.firebase.Firebase
8+
import dev.gitlive.firebase.FirebaseApp
89
import dev.gitlive.firebase.FirebaseOptions
910
import dev.gitlive.firebase.apps
1011
import dev.gitlive.firebase.internal.decode
@@ -85,6 +86,7 @@ class FirebaseFirestoreTest {
8586
)
8687
}
8788

89+
lateinit var firebaseApp: FirebaseApp
8890
lateinit var firestore: FirebaseFirestore
8991

9092
@BeforeTest
@@ -100,14 +102,15 @@ class FirebaseFirestoreTest {
100102
gcmSenderId = "846484016111",
101103
),
102104
)
105+
firebaseApp = app
103106

104107
firestore = Firebase.firestore(app).apply {
105-
useEmulator(emulatorHost, 8080)
106-
settings = firestoreSettings(settings) {
108+
settings = firestoreSettings {
107109
cacheSettings = memoryCacheSettings {
108110
gcSettings = memoryEagerGcSettings { }
109111
}
110112
}
113+
useEmulator(emulatorHost, 8080)
111114
}
112115
}
113116

@@ -1031,6 +1034,12 @@ class FirebaseFirestoreTest {
10311034
fieldQuery.assertDocuments(FirestoreTest.serializer(), testOne)
10321035
}
10331036

1037+
@Test
1038+
fun testMultiple() = runTest {
1039+
Firebase.firestore(firebaseApp).disableNetwork()
1040+
Firebase.firestore(firebaseApp).enableNetwork()
1041+
}
1042+
10341043
private suspend fun setupFirestoreData(
10351044
documentOne: FirestoreTest = testOne,
10361045
documentTwo: FirestoreTest = testTwo,

firebase-firestore/src/iosMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,11 @@ import dev.gitlive.firebase.firestore.NativeFirebaseFirestore
66
import dev.gitlive.firebase.firestore.NativeTransaction
77
import dev.gitlive.firebase.firestore.await
88
import dev.gitlive.firebase.firestore.awaitResult
9-
import dev.gitlive.firebase.firestore.firestoreSettings
10-
import dev.gitlive.firebase.firestore.memoryCacheSettings
119
import kotlinx.coroutines.runBlocking
1210

1311
@Suppress("UNCHECKED_CAST")
1412
internal actual class NativeFirebaseFirestoreWrapper internal actual constructor(actual val native: NativeFirebaseFirestore) {
1513

16-
actual var settings: FirebaseFirestoreSettings = firestoreSettings { }.also {
17-
native.settings = it.ios
18-
}
19-
set(value) {
20-
field = value
21-
native.settings = value.ios
22-
}
23-
2414
actual fun collection(collectionPath: String) = native.collectionWithPath(collectionPath)
2515

2616
actual fun collectionGroup(collectionId: String) = native.collectionGroupWithID(collectionId)
@@ -33,6 +23,10 @@ internal actual class NativeFirebaseFirestoreWrapper internal actual constructor
3323
actual fun setLoggingEnabled(loggingEnabled: Boolean): Unit =
3424
FIRFirestore.enableLogging(loggingEnabled)
3525

26+
actual fun applySettings(settings: FirebaseFirestoreSettings) {
27+
native.settings = settings.ios
28+
}
29+
3630
actual suspend fun <T> runTransaction(func: suspend NativeTransaction.() -> T) =
3731
awaitResult<Any?> {
3832
native.runTransactionWithBlock(
@@ -46,10 +40,8 @@ internal actual class NativeFirebaseFirestoreWrapper internal actual constructor
4640

4741
actual fun useEmulator(host: String, port: Int) {
4842
native.useEmulatorWithHost(host, port.toLong())
49-
settings = firestoreSettings(settings) {
50-
this.host = "$host:$port"
51-
cacheSettings = memoryCacheSettings { }
52-
sslEnabled = false
43+
native.settings = native.settings.apply {
44+
this.sslEnabled = false
5345
}
5446
}
5547

firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/firestore.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ public actual fun Firebase.firestore(app: FirebaseApp): FirebaseFirestore =
3939

4040
internal actual data class NativeFirebaseFirestore(val js: JsFirestore)
4141

42-
public operator fun FirebaseFirestore.Companion.invoke(js: JsFirestore): FirebaseFirestore = FirebaseFirestore(NativeFirebaseFirestore(js))
42+
public operator fun FirebaseFirestore.Companion.invoke(js: JsFirestore): FirebaseFirestore = FirebaseFirestore(
43+
NativeFirebaseFirestore(js),
44+
)
4345
public val FirebaseFirestore.js: JsFirestore get() = native.js
4446

4547
public actual data class FirebaseFirestoreSettings(

firebase-firestore/src/jsMain/kotlin/dev/gitlive/firebase/firestore/internal/NativeFirebaseFirestoreWrapper.kt

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import dev.gitlive.firebase.firestore.NativeWriteBatch
1010
import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence
1111
import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator
1212
import dev.gitlive.firebase.firestore.externals.doc
13+
import dev.gitlive.firebase.firestore.externals.getFirestore
1314
import dev.gitlive.firebase.firestore.externals.initializeFirestore
1415
import dev.gitlive.firebase.firestore.externals.setLogLevel
1516
import dev.gitlive.firebase.firestore.externals.writeBatch
@@ -21,38 +22,45 @@ import kotlinx.coroutines.await
2122
import kotlinx.coroutines.promise
2223

2324
internal actual class NativeFirebaseFirestoreWrapper internal constructor(
24-
private val createNative: NativeFirebaseFirestoreWrapper.() -> NativeFirebaseFirestore,
25+
private val createNative: NativeFirebaseFirestoreWrapper.(FirebaseFirestoreSettings?) -> NativeFirebaseFirestore,
2526
) {
2627

27-
internal actual constructor(native: NativeFirebaseFirestore) : this({ native })
28+
internal actual constructor(native: NativeFirebaseFirestore) : this(
29+
{ settings ->
30+
settings?.let {
31+
NativeFirebaseFirestore(initializeFirestore(native.js.app, settings))
32+
} ?: native
33+
},
34+
)
2835
internal constructor(app: FirebaseApp) : this(
29-
{
36+
{ settings ->
3037
NativeFirebaseFirestore(
31-
initializeFirestore(app, settings.js).also {
32-
emulatorSettings?.run {
33-
connectFirestoreEmulator(it, host, port)
38+
settings?.let {
39+
initializeFirestore(app, it.js).also {
40+
emulatorSettings?.run {
41+
connectFirestoreEmulator(it, host, port)
42+
}
3443
}
35-
},
44+
} ?: getFirestore(app),
3645
)
3746
},
3847
)
3948

4049
private data class EmulatorSettings(val host: String, val port: Int)
4150

42-
actual var settings: FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().build()
51+
private var settings: FirebaseFirestoreSettings? = null
4352
set(value) {
4453
if (lazyNative.isInitialized()) {
45-
throw IllegalStateException("FirebaseFirestore has already been started and its settings can no longer be changed. You can only call setFirestoreSettings() before calling any other methods on a FirebaseFirestore object.")
46-
} else {
47-
field = value
54+
createNative(value) // Call initialize again to ensure native crash occurs if settings have changed
4855
}
56+
field = value
4957
}
5058
private var emulatorSettings: EmulatorSettings? = null
5159

5260
// initializeFirestore must be called before any call, including before `getFirestore()`
5361
// To allow settings to be updated, we defer creating the wrapper until the first call to `native`
5462
private val lazyNative = lazy {
55-
createNative()
63+
createNative(settings)
5664
}
5765
actual val native: NativeFirebaseFirestore by lazyNative
5866
private val js get() = native.js
@@ -89,6 +97,10 @@ internal actual class NativeFirebaseFirestoreWrapper internal constructor(
8997
actual fun setLoggingEnabled(loggingEnabled: Boolean) =
9098
rethrow { setLogLevel(if (loggingEnabled) "error" else "silent") }
9199

100+
actual fun applySettings(settings: FirebaseFirestoreSettings) {
101+
this.settings = settings
102+
}
103+
92104
@OptIn(DelicateCoroutinesApi::class)
93105
actual suspend fun <T> runTransaction(func: suspend NativeTransaction.() -> T) =
94106
rethrow {
@@ -102,8 +114,10 @@ internal actual class NativeFirebaseFirestoreWrapper internal constructor(
102114
rethrow { clearIndexedDbPersistence(js).await() }
103115

104116
actual fun useEmulator(host: String, port: Int) = rethrow {
105-
settings = firestoreSettings(settings) {
106-
this.host = "$host:$port"
117+
if (settings != null) {
118+
settings = firestoreSettings(settings) {
119+
this.host = "$host:$port"
120+
}
107121
}
108122
emulatorSettings = EmulatorSettings(host, port)
109123
}

0 commit comments

Comments
 (0)