Detecting Connection State Called Twice - ios

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")
}
})
I'm using this to detect my connection state to my firebase. My problem is when their is internet connection the result goes "Not Connected" then afterwards goes "Connected". When their is no internet connection it just goes directly to "Not Connected". Can someone please explain?

What you're seeing is the expected behavior.
The .info/connected flag determines whether the app/client is connected to the Firebase Database backend. While this of course requires that you have an internet connection, there is more to it than that. That's why it is possible for .info/connected to be false even though you have a working internet connection.
This is especially true when you start the app. It takes a few moments after the app starts before the Firebase client is connected to its database server, so usually the .info/connected value starts as false and then becomes true. Sometimes it even toggles a few times before settling.
Also see:
How to handle internet connection status Firebase
Firebase connection state listener returns false in javascript

Related

How to handle network connectivity issues (Firebase FireStore)?

For testing, I've turned off networking. I've got caching disabled:
settings.isPersistenceEnabled = false // https://firebase.google.com/docs/firestore/manage-data/enable-offline -- done to help demonstrate async handling
I'm noticing that error is not set even though I see this in my logs:
2018-08-04 10:26:26.441529-0400 Behavior-based Notifications
prototype[52077:2250670] 5.4.1 - [Firebase/Firestore][I-FST000001]
Could not reach Cloud Firestore backend. Connection failed 2 times.
Most recent error: Error Domain=FIRFirestoreErrorDomain Code=2
"Network error (such as timeout, interrupted connection or unreachable
host) has occurred." UserInfo={NSLocalizedDescription=Network error
(such as timeout, interrupted connection or unreachable host) has
occurred.} This typically indicates that your device does not have a
healthy Internet connection at the moment. The client will operate in
offline mode until it is able to successfully connect to the backend.
Here's relevant code snippet:
DBIFirebase.db.collection(URI).getDocuments() {
(snapshot, error) in
if tx < 1 {
print("returned from Firebase TX when tx count is exhausted, dropping")
return
}
if let error = error {
// in either case, there was a previous error
print("Firestore error loading data for app \(app) - \(error.localizedDescription)")
callback(samples, Errors.firebaseError)
tx = 0
return
}
error is always nil. documents are always empty.
edit:
Very near after that if let error, I use this to process the documents:
guard let documents = snapshot?.documents else {
print("no documents for app \(app)")
callback(samples, nil)
tx = 0
return
}
for document in documents {
I set a breakpoint. error is seen to be nil. stepping into this block skips over it (the guard block doesn't trigger, I'm talking about stepping into from a breakpoint at the for loop). If I turn networking on, it goes into it (not a swifty caching loops thing).
How would I distinguish between an empty but successful return and a network connectivity issue?

Ondisconnect is fired if app goes to background mode

I have the following code:
func OnlineStatus(userID: String){
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// User is signed in.
self.UID = user.uid
self.connectedRef.observe(.value, with: { snapshot in
if let connected = snapshot.value as? Bool, connected {
// print("############################ Connected")
self.ref.child(self.UID!).child("OnlineStatus").setValue("ON")
} else {
// print("############################ Not connected")
self.ref.child(self.UID!).child("OnlineStatus").setValue("OFF")
}
self.ref.child(self.UID!).child("OnlineStatus").onDisconnectSetValue("OFF")
})
}}
}
The function will be triggered in viewWillAppear. The idea is to build a simple presence system. For some reason onDisconnect gets fired when I send the app to background and than send my iPhone to sleep. I actually would like that online status goes to off only when user logs out or looses internet connection. What is wrong with my code or settings?
The onDisconnect event fires when the client disconnects from the Firebase Database servers, and that happens when your app goes to the background. There is no difference from Firebase's perspective between the user being on train that drives into a tunnel, and their phone going to sleep. In both cases the connection between the client and the server gets dropped, so the onDisconnect() fires.
You'll typically end up using .info/connected and onDisconnect() to set a value of when the user was last seen, while using onAuthStateChanged() to set a status flag of the user being signed in. Then you show the list of users by first showing users that are signed in, in the order of how recently they were active.

Pattern for retrying URLSession dataTask?

I'm fairly new to iOS/Swift development and I'm working on an app that makes several requests to a REST API. Here's a sample of one of those calls which retrieves "messages":
func getMessages() {
let endpoint = "/api/outgoingMessages"
let parameters: [String: Any] = [
"limit" : 100,
"sortOrder" : "ASC"
]
guard let url = createURLWithComponents(endpoint: endpoint, parameters: parameters) else {
print("Failed to create URL!")
return
}
do {
var request = try URLRequest(url: url, method: .get)
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
if let error = error {
print("Request failed with error: \(error)")
// TODO: retry failed request
} else if let data = data, let response = response as? HTTPURLResponse {
if response.statusCode == 200 {
// process data here
} else {
// TODO: retry failed request
}
}
}
task.resume()
} catch {
print("Failed to construct URL: \(error)")
}
}
Of course, it's possible for this request to fail for a number of different reasons (server is unreachable, request timed out, server returns something other than 200, etc). If my request fails, I'd like to have the ability to retry it, perhaps even with a delay before the next attempt. I didn't see any guidance on this scenario in Apple's documentation but I found a couple of related discussions on SO. Unfortunately, both of those were a few years old and in Objective-C which I've never worked with. Are there any common patterns or implementations for doing something like this in Swift?
This question is airing on the side of opinion-based, and is rather broad, but I bet most are similar, so here goes.
For data updates that trigger UI changes:
(e.g. a table populated with data, or images loading) the general rule of thumb is to notify the user in a non-obstructing way, like so:
And then have a pull-to-refresh control or a refresh button.
For background data updates that don't impact the user's actions or behavior:
You could easily add a retry counter into your request result depending on the code - but I'd be careful with this one and build out some more intelligent logic. For example, given the following status codes, you might want to handle things differently:
5xx: Something is wrong with your server. You may want to delay the retry for 30s or a minute, but if it happens 3 or 4 times, you're going to want to stop hammering your back end.
401: The authenticated user may no longer be authorized to call your API. You're not going to want to retry this at all; instead, you'd probably want to log the user out so the next time they use your app they're prompted to re-authenticate.
Network time-out/lost connection: Retrying is irrelevant until connection is re-established. You could write some logic around your reachability handler to queue background requests for actioning the next time network connectivity is available.
And finally, as we touched on in the comments, you might want to look at notification-driven background app refreshing. This is where instead of polling your server for changes, you can send a notification to tell the app to update itself even when it's not running in the foreground. If you're clever enough, you can have your server repeat notifications to your app until the app has confirmed receipt - this solves for connectivity failures and a myriad of other server response error codes in a consistent way.
I'd categorize three methods for handling retry:
Reachability Retry
Reachability is a fancy way of saying "let me know when network connection has changed". Apple has some snippets for this, but they aren't fun to look at — my recommendation is to use something like Ashley Mill's Reachability replacement.
In addition to Reachability, Apple provides a waitsForConnectivity (iOS 11+) property that you can set on the URLSession configuration. By setting it, you are alerted via the URLSessionDataDelegate when a task is waiting for a network connection. You could use that opportunity to enable an offline mode or display something to the user.
Manual Retry
Let the user decide when to retry the request. I'd say this is most commonly implemented using a "pull to refresh" gesture/UI.
Timed/Auto Retry
Wait for a few second and try again.
Apple's Combine framework provides a convenient way to retry failed network requests. See Processing URL Session Data Task Results with Combine
From Apple Docs: Life Cycle of a URL Session (deprecated)... your app should not retry [a request] immediately, however. Instead, it should use reachability APIs to determine whether the server is reachable, and should make a new request only when it receives a notification that reachability has changed.

Firebase Query Error Handling

I've got some functions to read data from Firebase, but sometimes I never get a response (or it's massively delayed). I read here that maybe Firebase can close the socket connection before data is received. It looks like someone had a similar issue here, but never posted a solution.
Here's a sample of my code for downloading user data from Firebase.
// loads the current user's information
static func loadUserDataWithCompletion(completion: (UserInfo) -> Void) {
let ref = FIRDatabase.database().reference()
print("loading current user data...")
let uid = (FIRAuth.auth()?.currentUser?.uid)!
ref.child("users").queryOrderedByKey().queryEqualToValue(uid).observeEventType(.ChildAdded, withBlock: { (snapshot) in
print("found user data!")
if let dictionary = snapshot.value as? [String:AnyObject] {
let info = userFromDict(dictionary)
// execute code slated for completion
completion(info)
}
})
}
Is there some way I can detect errors using observeEventType? Maybe then I'd at least get more information about why the issue is happening.
There can be three possible errors that you might face while observing your database :-
You haven't defined a proper path to your database observe query
You are parsing in a wrong or faulty manner
You don't have the permission to access that particular node at that particular time(Security Rules)
For the first two you have to take care of it yourself, But for the third condition you can use withCancel block:-
FIRDatabase.database().reference().child("users").queryOrderedByKey().queryEqual(toValue: "uid").observe(.childAdded, with: { (snapshot) in
//your code
}, withCancel: {(err) in
print(err) //The cancelBlock will be called if you will no longer receive new events due to no longer having permission.
})
As far as error handling if the user looses network connection while Writing, Your app remains responsive regardless of network latency or connectivity. See the Write data offline section of Firebase Docs

Repeat an asynchronous request if it fails?

I am writing an ios app that relies on being able to tell when a user is connected to wifi and, when he or she connects or disconnects, send an asynchronous request using alamo fire.
The first time I connect, my asynchronous succeeds.
However, after I first connect, any toggling of the wifi results in 404s.
I suspect this is because I am sending the request as soon as the user connects/disconnects, meaning that for a brief moment he or she has no internet service.
My question is, can I repeat the request if it fails or is it possible to "cache" the requests I want to make and wait until the user has internet connection to make them?
There are many solutions to solve this. One is to call the download method recursively again and so implementing an automatic retry mechanism on errors:
func downloadSomething() {
Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
.response { request, response, data, error in
if let error = error {
log(error)
self.downloadSomething() // recursive call to downloadSomething
} else {
// do something on success
}
}
}
You can extend this by:
showing the user also an altert view asking him if he want's to retry
the download or not before retrying the download. (depending on your
UI strategy on network errors)
a specified count of automatic re-trys and then ask the user.
checking the error status code and then depending on the code do
different network error handling strategies...
etc...
I think there is no needed to re-invented apple code like reachability or this swift reachability porting. You can able to check if a user is connected to the net or wifi very easily:
class func hasConnectivity() -> Bool {
let reachability: Reachability = Reachability.reachabilityForInternetConnection()
let networkStatus: Int = reachability.currentReachabilityStatus().rawValue
return networkStatus != 0
}
For a Wi-Fi connection:
(reachability.currentReachabilityStatus().value == ReachableViaWiFi.value)

Resources