-
Notifications
You must be signed in to change notification settings - Fork 160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed a crash for settings when iOS would access the same Firebase instance twice #562
Changes from 3 commits
a9086b8
457926f
be243a9
09c1e71
fef2847
4be99fd
c987bea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ import dev.gitlive.firebase.firestore.NativeWriteBatch | |
import dev.gitlive.firebase.firestore.externals.clearIndexedDbPersistence | ||
import dev.gitlive.firebase.firestore.externals.connectFirestoreEmulator | ||
import dev.gitlive.firebase.firestore.externals.doc | ||
import dev.gitlive.firebase.firestore.externals.getFirestore | ||
import dev.gitlive.firebase.firestore.externals.initializeFirestore | ||
import dev.gitlive.firebase.firestore.externals.setLogLevel | ||
import dev.gitlive.firebase.firestore.externals.writeBatch | ||
|
@@ -20,28 +21,41 @@ import kotlinx.coroutines.GlobalScope | |
import kotlinx.coroutines.await | ||
import kotlinx.coroutines.promise | ||
|
||
// There is currently no way to check whether Firestore was already initialized for a given app without actually initializing it | ||
// Therefore we keep track of this internally | ||
private val appsWithFirestore = mutableListOf<FirebaseApp>() | ||
|
||
internal actual class NativeFirebaseFirestoreWrapper internal constructor( | ||
private val createNative: NativeFirebaseFirestoreWrapper.() -> NativeFirebaseFirestore, | ||
private val canUpdateSettings: () -> Boolean, | ||
) { | ||
|
||
internal actual constructor(native: NativeFirebaseFirestore) : this({ native }) | ||
internal actual constructor(native: NativeFirebaseFirestore) : this({ native }, { false }) | ||
internal constructor(app: FirebaseApp) : this( | ||
{ | ||
NativeFirebaseFirestore( | ||
initializeFirestore(app, settings.js).also { | ||
emulatorSettings?.run { | ||
connectFirestoreEmulator(it, host, port) | ||
if (appsWithFirestore.contains(app)) { | ||
getFirestore(app) | ||
} else { | ||
initializeFirestore(app, settings.js).also { | ||
emulatorSettings?.run { | ||
connectFirestoreEmulator(it, host, port) | ||
} | ||
appsWithFirestore.add(app) | ||
} | ||
}, | ||
) | ||
}, | ||
{ | ||
!appsWithFirestore.contains(app) | ||
}, | ||
) | ||
|
||
private data class EmulatorSettings(val host: String, val port: Int) | ||
|
||
actual var settings: FirebaseFirestoreSettings = FirebaseFirestoreSettings.Builder().build() | ||
set(value) { | ||
if (lazyNative.isInitialized()) { | ||
if (lazyNative.isInitialized() || !canUpdateSettings()) { | ||
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.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought we removed this check in favour of letting the native sdk throw the exception? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The thing is that there is no native sdk to fall back to, as explained in the other remark. We shouldnt be calling initializeFirestore here yet, as settings may still be updated after this. |
||
} else { | ||
field = value | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what are we doing exactly that is straightforward on android but not supported natively on ios and js? The other option here would be to cater for the lowest common denominator, in other words don't support it on android either, but I'm still not exactly clear what the differences are?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So there are two things:
First and foremost, the biggest difference between the mobile platforms and JS is that for the mobile platforms, the FirebaseFirestore object internally keeps a state machine that allows it to change settings until it is first used. So, you can do:
Javascript on the other hand has no such stateMachine with regards to settings. You can either get a FirebaseFirestore instance with whatever settings it was previously initialized, or you can initialize it with settings provided it has not been initialized before (there's no telling whether it is btw). The only one you can do is enable the emulator, which you can do after initialization but only before doing any operations.
This difference is the main reason for this major discrepancy. The approach of this library has always been to get the FirebaseFirestore object and change its settings before operations are executed. If you look at the
setSettings
method on JS pre 1.13.0, you'll realize that it would've crashed if you called the method as it calls the initializationCode after it has already gotten an initialized instance.I guess it might be possible to essentially remove the state machine logic out of the library, so that you must provide the settings when getting a FirebaseFirestore instance, but this might have unknown side effects and is a breaking change for sure.
The second problem is smaller, and could be solved by omission: Settings on Android are fully readable, on iOS readable except for the cache settings, and on JS (due to initialization mentioned above) not readable at all. As of this PR, we get the settings on Android/iOS (though we miss a few cache settings on iOS) and we track it ourselves for JS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok well for the second issue I think removing the getter so you can't read settings on any platform is sensible.
As for the first issue, looking at https://firebase.google.com/docs/firestore/manage-data/enable-offline#configure_offline_persistence, I suggest the following:
firestore.settings.host = "1.1.1.1
shouldn't be possible (which will be the case once the getter is removed)Firebase.firestore.settings = firestoreSettings { ... }
needs to callinitializeFirestore
beforegetFirestore
is called can the js property be lazy sogetFirestore
is not called doingFirebase.firestore.settings = firestoreSettings { ... }
getFirestore
has already been called then let the js sdk (and others) throw the relevant error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed:
setSettings(builder)
andapplySettings(settings)
methods. This is an api change compared to 1.13.0 but I think we're both fine with that.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets keep the syntax matching the android sdk with a setter only property rather that two new functions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its not an officially supported feature, so it has some downsides as your link shows. Regardless, Ive implemented it as requested