SwiftUI & Firestore - swift

I am wanting to use Firestore to retrieve user info and other data linked to that user once they have logged in via firebase auth. On the home page of the app I use .onAppear{ pulluserData() }. I understand that Firestore functions are asynchronous so how can I wait for this data to be pulled before displaying it to the user on the home screen?
Here is my function to check the database:
func checkDatabase() async {
//Function that will check the database. Will be good to add a listener eventually
if self.pullUserData{
await dbm.readUser(userID: "VSWAq7QCw3dbGYwMdtClbbANGVe2")
}
}
and the actual database function:
func readUser(userID: String)async{
//Function that will be used to read user info from the database
let userRef = database.collection("users")
do{
let doc = try await userRef.document(userID).getDocument().data()
print("The doc is: ")
print(doc as Any)
}
catch {
}
}

There are a number of ways to do this but the two most common ways are to (1) use a launch screen to indicate a loading state that disappears to a view identical to the launch screen (to continue the appearance of loading) that is only removed when the database returns (i.e. Twitter); (2) load the user right into the app and allow them to move freely while either indicating that data is loading or displaying cached data.
Remember that Firestore maintains a local cache on the device which means that data will be available immediately when the app launches. This data may be out of sync with the server but it will update as soon as the app establishes a connection with Firestore, which is usually instant. What I would recommend is launching the user right into the app without the use of a loading screen and relying on the cached data to get the UI up as fast as possible.
And if we're only talking about user-specific data (data that is specific to the user that the user has full control over) then that data will only change when the user changes it, which would have been the last time they used the app, which means that the locally-cached data on their device (assuming they use only one device) will always reflect the state of the server (in theory, anyway). And if it doesn't then it doesn't; the fresh data will update instantly anyway.
You may then wonder what happens if the user launches the app without connection. In that case, the cached data is displayed and the user is almost none the wiser. And because Firestore is offline capable, the user can freely edit their data and it will write to the server when connection eventually establishes.

Related

Why isn't my firebase listener firing when the app is offline and initialized offline

I've setup a listener on my database that is only failing to respond in a weird offline scenario.
On setup I'm adding a listener to a database that should fire anytime there's an update...
let mySpots = self.databaseRefSpots.child("users").child(globalMasterUser.userUID).child("type").observe(.value, with: { snapshot in
//code that should be executed when there's an update.
Once the user takes some action that should get saved, it's saved to the firebase database using...
annotationdb.child("type").setValue("Feature")
annotationdb.child("properties").setValue(propDict)
annotationdb.child("geometry").setValue(["type":"Point"])
annotationdb.child("geometry").child("coordinates").setValue(coordinates)
This code works whenever the user is online, and when a user starts the app online and goes offline.
But, if the listener is created when the app is initialized in an offline state, it looks like the listener is setup successfully, but after my setValue's are called, the listener isn't called. If I turn the data connection on, at that point my listeners are called with the values I set with setValue.
Any idea why the listeners aren't firing in this offline state?
I've tried stepping through the firebase code to see if the listener is getting hit and it's not. I've tried creating separate listeners to catch this scenario and they wont fire either.

How do I add a widget to my web app in run time permanently?

I am coding an web app that allows the user to create a type of electronic document using widgets (that I then store in a list view). Is it possible to add this list view to my web app in run time so other users on other devices can access the electronic form?
The only way to do this would be to use some sort of database to hold the resulting "document". You could use Firebase Firestore, the server's file system, or anything else you can connect to. There is no way to easily modify the underlying code, as it is no longer Dart code; it gets transpiled to JavaScript.
Assuming you go with Firestore, you could have something like this:
CollectionReference documentRef = Firestore.instance
.collection('forms')
.document(formId);
documentRef.snapshots().listen((DocumentSnapshot documentSnapshot) {
// Do something with `documentSnapshot`.
// setState(() => _updateForm(documentSnapshot.data.data()));
});

How to detect absent network connection when setting Firestore document

We are building a real-time chat app using Firestore. We need to handle a situation when Internet connection is absent. Basic message sending code looks like this
let newMsgRef = database.document(“/users/\(userId)/messages/\(docId)“)
newMsgRef.setData(payload) { err in
if let error = err {
// handle error
} else {
// handle OK
}
}
When device is connected, everything is working OK. When device is not connected, the callback is not called, and we don't get the error status.
When device goes back online, the record appears in the database and callback triggers, however this solution is not acceptable for us, because in the meantime application could have been terminated and then we will never get the callback and be able to set the status of the message as sent.
We thought that disabling offline persistence (which is on by default) would make it trigger the failure callback immediately, but unexpectedly - it does not.
We also tried to add a timeout after which the send operation would be considered failed, but there is no way to cancel message delivery when the device is back online, as Firestore uses its queue, and that causes more confusion because message is delivered on receiver’s side, while I can’t handle that on sender’s side.
If we could decrease the timeout - it could be a good solution - we would quickly get a success/failure state, but Firebase doesn’t provide such a setting.
A built-in offline cache could be another option, I could treat all writes as successful and rely on Firestore sync mechanism, but if the application was terminated during the offline, message is not delivered.
Ultimately we need a consistent feedback mechanism which would trigger a callback, or provide a way to monitor the message in the queue etc. - so we know for sure that the message has or has not been sent, and when that happened.
The completion callbacks for Firestore are only called when the data has been written (or rejected) on the server. There is no callback for when there is no network connection, as this is considered a normal condition for the Firestore SDK.
Your best option is to detect whether there is a network connection in another way, and then update your UI accordingly. Some relevant search results:
Check for internet connection with Swift
How to check for an active Internet connection on iOS or macOS?
Check for internet connection availability in Swift
As an alternatively, you can check use Firestore's built-in metadata to determine whether messages have been delivered. As shown in the documentation on events for local changes:
Retrieved documents have a metadata.hasPendingWrites property that indicates whether the document has local changes that haven't been written to the backend yet. You can use this property to determine the source of events received by your snapshot listener:
db.collection("cities").document("SF")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
let source = document.metadata.hasPendingWrites ? "Local" : "Server"
print("\(source) data: \(document.data() ?? [:])")
}
With this you can also show the message correctly in the UI

How to atomically delete user and user's data from Firebase Auth and Realtime Database respectively?

Some background
When a user account is created, I do 3 things using callback chaining in the sequence 1 > 2 > 3.
A user is created in Firebase Auth (the standard way using createUser(withEmail ...))
I upload the user's profile picture to Firebase Storage and capture the returned downloadUrl for use in step 3
I store the user's other information (including the downloadUrl from step 2) in a node in the realtime database (keyed by $userid)
Now the problem
I provide a button called 'Delete account' which should enable the user to delete everything. That is, clear all their data in the Realtime Database, clear their profile picture in Firebase Storage, and finally delete their account from Auth. The important thing is that all these operations should succeed or be canceled if even one fails.
I've gone through ~10 pages of S/O questions & answers, there was 1 unanswered question like this one (it asked about the account creation process...I suppose an answer to that question can easily be adapted here.)
What have i tried?
Currently, I use callback chaining like so:
// start by atomically deleting all the user data from the Realtime Database using the fanout system.
- get all appropriate locations and save in fanout dictionary
- update all these locations to nil // atomic goodness :)
-callback:
-on failure:
- just return // no worries, nothing has changed yet :/
-on success:
// proceed to delete user files on firebase storage
- delete path $userid on firebase storage
-callback:
-on failure: // this is bad, no idea what to do :(
-on success:
// proceed to delete account from Auth
- delete user account from Auth
-callback:
-on failure: // this is terrible, also, it could happen often b/c firebase does ask for re-authentication sometimes :(
-on success: // thank goodness! I have an authListener somewhere ready to show the 'signInViewController' :)
How do you handle such multi-system (Auth, Storage, RealtimeDB) operation atomically? I have looked into transactions but can't see how they can be adapted for this - the docs only show them being used in incrementing counters for likes/stars etc in the RealtimeDB.
Any help will be very much appreciated.
So the main problem is what to do if the data you're trying to delete is being deleted only partially.
I think you should think about a method that will help you restore it if something goes wrong.
I'm not on my computer now but I suggest you to try to download the data that you want to delete and only if there were a success you delete the local copy too, otherwise you should be able to restore it easily.
Edit: if we talk about a serious amount of storage instead of a local copy you can make it on the cloud.
So you first copy this data, then delete it, then delete the database data (also copied previously) then you can delete the user account and finally you can delete the copy.

How to get recordsChanged sync status using JS Datastore API?

I'm using the JavaScript SDK flavor of the Dropbox Datastore API with a web app for mobile and desktop. When the recordsChanged event fires while the app is offline, object data about those changes are generated but the changes can't sync to the datastore until the app is online again.
The event data can be checked against the settings table, for instance, like this:
e.affectedRecordsForTable("settings")
But the array data returned has a lot of layers to wade through.
[t_datastore: t_deleted: false_managed_datastore: t_record_cache: t_rid: "startDate"_tid: "settings"__proto__: t]
I would like to capture the "has been synced" or the "not yet synced" status of each change (each array index) so that I can store the data still waiting to sync in case the session is lost (user closes the app/browser or OS kills the app process). But I also want to know if/when the data does eventually sync successfully. Where can I find the property holding this data?
I found my answer. Steve Marx has a post on the Dropbox developer blog that covers the information I needed. There is a datastore.getSyncStatus().uploading property that returns true or false depending on the state of the datastore sync status.
Source:
https://www.dropbox.com/developers/blog/61/checking-the-datastore-sync-status-in-javascript