I am building iOS App using Firebase, I found that the method observeSingleEvent(with or without cancel block) in Firebase would not even fire if the app is not connected to the network and there is no cached value for the location.
I need to show messages to users when the App lose connection to internet but no need to do it if there is cached value for the location when use keepSynced to it.
How can I do if the API without an error that can detect connect or not in return? Since the document says the cancelBlock will be called if you don't have permission to access this data, but it even not callback when the App without connection.
if and else statetment check your problem ? If it is connected, you will be call the observeSingleEvent function
let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
if snapshot.value as? Bool ?? false {
print("Connected")
} else {
print("Not connected")
}
})
More detail : Detecting Connection State
Related
I'm newly learning iOS/swift programming and developing an application using MongoDB Realm and using Realm sync. I'm new to programming and realm, so please feel free to correct any terminology. My question is about listening for realm notifications, which I see referred to as change listeners and notification tokens. Regardless, here is the info:
My application has a list of locations with a status (confirmed/pending/cancelled). I open this list from my realm as a realm managed object and create my notification handler:
//This is called in it's own function, but assigns the locations
locations = publicRealm?.objects(Location.self)
//This is run after the function is called
self?.notificationToken = self?.locations!.observe { [weak self] (_) in
self?.tableView.reloadData()
print("Notification Token!!!")
I then populate my table view and let a user tap on a location, which passes the location and realm to another view controller where the user can update the status. That update is made in a separate view controller.
do{
try publicRealm?.write {
selectedLocation?.statusMessage = locationStatusTextField.text!
selectedLocation?.status = selectedStatus
}
}catch{
print("Error saving location data: \(error)")
}
At this point my notification token is successfully triggered on the device where I am making the location update. The change is shown immediately. However there is no notification token or realm refresh that happens on any other open devices that are showing the locations table view. They do not respond to the change, and will only respond to it if I force realm.refresh(). The change is showing in Atlas on MongoDB server, though.
I am testing on multiple simulators and my own personal phone as well, all in Xcode.
I'm very confused how my notification token can trigger on one device but not another.
When I first started the project it was a much simpler realm model and I could run two devices in simulator and updating one would immediately fire a change notification and cause the second device to show the correct notification.
I have since updated to a newer realm version and also made the realm model more complicated. Though for this purpose I am trying to keep it simple by doing all changes via swift and in one realm.
I also have realm custom user functions running and changing data but I think reading the docs I am realizing that those will not trigger a notification - I'm not sure if that's true though? I just know right now that if I change data in the DB via a user function no notifications are triggered anywhere - but if I do realm.refresh() then the change shows.
What is it that I am missing in how I am using these notifications?
***Updating information on Public Realm:
Save the realm:
var publicRealm:Realm?
Login as an anon user and then open the realm:
let configuration = user.configuration(partitionValue: "PUBLIC")
Realm.asyncOpen(configuration: configuration) { [weak self](result) in
DispatchQueue.main.async {
switch result {
case .failure(let error):
fatalError("Failed to open realm: \(error)")
case .success(let publicRealm):
self!.publicRealm = publicRealm
guard let syncConfiguration = self?.publicRealm?.configuration.syncConfiguration else {
fatalError("Sync configuration not found! Realm not opened with sync?")
}
It is after this realm opening that the locations are loaded and notification token is created.
I use a segue to pass the location object and realm to the next VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! UpdateLocationViewController
destinationVC.delegate = self
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedLocation = locations?[indexPath.row]
}
if indexPathRow != nil {
destinationVC.selectedLocation = locations?[indexPathRow!]
}
destinationVC.publicRealm = self.publicRealm
}
Some notes:
original public realm opened by anon user
only a logged in user can click on a location... so in the 'UpdateLocation' VC that gets passed the public realm I am a logged in user. But, I'm just using the Dev mode of Realm to allow me to read/write however I like... and I am writing straight to that public realm to try and keep it simple. (I have a custom user function that writes to both public and the user's org realm, but I stopped trying to use the function for now)
I identify the object to update based on the passed in location object from the first VC
I needed to use try! when making my write call rather than just try. I updated my write blocks as such:
try! publicRealm?.write {
selectedLocation?.statusMessage = locationStatusTextField.text!
selectedLocation?.status = selectedStatus
}
Just wanted to follow up in case anyone finds this. Thank you!
Ok something really weird happened. Have an app that's about to go into production. This is how the application functions
There is a Home Page which fetches the logged in user's document every time the app comes to foreground
This is not a one time fetch but a listener so it will also listen for changes as the app is in foreground
I use this mechanism to keep the user document up to date and pass it to other pages in the app
When app goes to background, I remove the listener
The user document has close to 30 fields
So I was testing another functionality in app in the simulator and everything was working. I parallely ran the app with the same user logged in on a real device and this is what happened after the build:
As the app came to foreground the listener fired without stopping. I have the code in a such a way that every time the listener fetches the document from firestore, a part of the home page animates. So technically the home page refreshed without stopping
I killed the app and re-built it and the continuous firestore fetch happened yet again
So I all together deleted the app and re-built it - Now the continuous firestore fetch stopped
Here is the PROBLEM:
The document has all
30 fields on firestore
But on the real device, it fetches only 2 fields
I tried re-installing and re-building many times but this is the state
But on the simulator with the same user logged in, it works fine.
What could be the issue? Is there some corrupted cache for this particular user? Firestore is a solid product so never encountered anything like this before.
Here is the listener code:
#objc func viewEntersForeground(){
guard let currentUid = Auth.auth().currentUser?.uid else {return}
let ref = Firestore.firestore().collection("users").document(currentUid)
userListener = ref.addSnapshotListener({ (snapshot, error) in
if let error = error{
let alertController = ErrorAlertController(errorText: "\(error.localizedDescription)", errorTitle: "Something's wrong", viewController: self)
alertController.showAlertController()
return
}
//THE BELOW DICTIONARY FETCHES ONLY TWO FIELDS
guard let dictionary = snapshot?.data() else {return}
self.userDictionary = dictionary
self.user = User(dictionary: dictionary)
self.checkIfUserExists()
if self.user?.uid == "" || self.user?.uid == nil {
return
}
self.passDataToInbox()
self.passDataToSettings()
self.checkIfFirTokenExists()
self.checkIfProfilePictureExists()
if self.user?.userState == 3{
self.isUserState3 = true
} else {
self.userState = self.user?.userState
}
})
}
I want to be able to remove a connection value from my app's real-time firebase database when they lose connection unexpectedly. This does not seem to be possible from what I have tried already.
I have tried using the "goOffline" function to properly close the sockets because from what I've heard, it doesn't close properly when you turn off your wifi.
func connect() {
let connectionsRef = self.rootRef.child("connections")
AF.request("https://projectname.cloudfunctions.net/Connect").response { response in
if response.data != nil {
if self.visiblename != nil {
connectionsRef.observeSingleEvent(of: .value, with: { snapshot in
for value in JSON(snapshot.value!).arrayValue {
if value["Address"].string! == self.visiblename {
let connectionRef = connectionsRef.child(String(value["Index"].int!))
connectionRef.keepSynced(true)
connectionRef.onDisconnectRemoveValue()
}
}
})
}
}
}
}
self.reachability.whenUnreachable = { _ in
Database.database().goOffline()
}
self.reachability.whenReachable = { _ in
Database.database().goOnline()
}
do {
try self.reachability.startNotifier()
} catch {}
It does automatically remove the value after around 60 seconds but I need my app to be able to handle any internet interruptions and remove the connection value quickly.
Also, if there is no available way to remove the value from the client when the client turns off their wifi. Is there a way to detect the disconnection from the server on the server itself? I have tried comparing date.getTime() to another date.getTime() variable that when you invoke the Connect request it updates the variable. Then the server watches but it didn't seem to work because for some reason it stopped watching the variable as soon as the client disconnected and doesn't have time to realize it. I assume this is because the server is based on cloud functions and has no reason to run when no clients are invoking it.
I have one function to get the realtime location updates of the selected user.
And in that function , i need to call the current status and history of that selected user from the firebase.
So, the problem is when I start my observer for location updates only , it's fine and it's great .
I get the value from firebase observer.
But , when i make a call for current status and history of that selected user , I am getting the delay in my firebase realtime observer .
I don't understand how come call to another firebase is affecting our realtime observer code.
Guys any suggestions,
I tried dispatching to background for current status /history call but still no luck.
Left me hopeless and clueless.
Added:
I am getting current status and realtime observers from the same node.
So , does this happens that same node is observing and same time if another firebase call is made to same node , is it colliding ???
UPDATED
Firebase function to get current status :
FIRDatabaseReference().child("personal").child(userUUID).child("realtime")
.observeSingleEvent(of: .value, with: { (snapshot) in
//print(snapshot.key)
if snapshot.exists(){
let location = LocationParser(dict: snapshot.value as! [String : AnyObject])?.location
completion(location)
}else{
completion(nil)
}
}){ (error) in
completion(nil)
debugPrint(error.localizedDescription)
}
Firebase function to observe location (continuously):
FIRDatabaseReference().child("personal").child(userUUID).child("realtime")
.observe(.value) { (snapshot:FIRDataSnapshot) in
print("\nOBSERVER FROM FIREBASE FIRED")
if let value = snapshot.value as? [String:AnyObject] {
if let location = LocationParser(dict: value)?.location {
//we cannot save location to our database
//sharedFirebaseManager.saveLocationHistoric(LocationParser(dict: value)!)
result(success(location))
}
}
}
QUESTION:
I am calling both from the same controller and I see continuous observer delays on calling another firebase function call.
Any clue.
I am following documentation located at: https://www.firebase.com/docs/ios/guide/offline-capabilities.html#section-connection-state
However, my implementation and test of:
connectionCheck.observeEventType(.Value, withBlock: { snapshot in
let connected = snapshot.value as? Bool
if connected != nil && connected! {
print("Connected")
} else {
print("Not connected")
}
})
The output in Xcode notes:
Not connected
Connected
If however I turn off the wifi, the result is simply:
Not connected
Given I wish to allow actions to occur and present to the user if there is not a connection, how can I ensure that this Firebase listener only returns the correct response once?
As a suggestion, you may want to add some additional functionality;
If you want to know about the users connection to Firebase, you should observe the .info/connected path, as stated in the docs.
The design pattern I use is to set up a global app variable called isConnected, and attach a Firebase observer to the .info/connected path.
Then in my app wherever I need to know if the user is connected or not, I attach a KVO Observer to my global isConnected var.
When the user disconnects (or connects), the Firebase observer is called which sets isConnected = false (or true).
Then, any places in my app that are observing the isConnected var is notified of the disconnect/connect and can take appropriate action.
Here's the code
let connectedRef = rootRef.childByAppendingPath(".info/connected")
connectedRef.observeEventType(.Value, withBlock: { snapshot in
self.isConnected = snapshot.value as! Bool //KVO property
//this is just so you can see it's being set, remove
if ( self.isConnected == true ) {
print("connected")
} else {
print("not connected")
}
// testing code
})