Ondisconnect is fired if app goes to background mode - ios

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.

Related

Connecting to the Secure Content in IOS

I am trying to connect to the portal object with the authenticated user which is cached and used throughout the app session, to provide the app with a view of a portal that is centered around a single user.
When the app is restarted, the credential must be reinstated, or the user must repeat the authentication process.
But every time when I connect it asks for username and password, I actually want to embed that into the code.
Any workarounds?
Below is my code.
self.portal = AGSPortal(url: URL(string: "https://www.arcgis.com")!, loginRequired: false)
self.portal.credential = AGSCredential(user: "theUser", password: "thePassword")
self.portal.load() {[weak self] (error) in
if let error = error {
print(error)
return
}
if self?.portal.loadStatus == AGSLoadStatus.loaded {
let fullName = self?.portal.user?.fullName
print(fullName!)
}
}
You can use AGSCredentialCache's enableAutoSyncToKeychainWithIdentifier:accessGroup:acrossDevices:accessible: to store credentials in the Keychain and when you re-launch the app, it won't prompt again. Please call this function at the start of the application using AGSAuthenticationManager.shared().credentialCache.
Regards,
Nimesh

Detecting Connection State Called Twice

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

Firebase Realtime Database Connection Refresh

I have two controllers one is login and another is tableView Controller where I am displaying users information.
When app starts it first checks if user is logged in or not.
If user is logged in, it will fetch users data from firebase realtime database and display in tableView. If not then it will show login controller where user have to login using email and password.
Now once i logged in i can see users data into TableViewController from firebase, But if i closed app without log out and open it after some time like 20 min later or next day when the app launch it check if user is logged in or not in my case it shows logged in and display tableViewController but it is empty it does not fetch users data from firebase realtime database.
If i will logged out and login again then i can see users data coming from firebase but if i close app and open it again after sometime it will not show any data into my tableViewController.
I dont want users to login every time in application to get firebase data if user is logged in it must show the data from firebase real time database.
Following is code to check if user is logged in or not
if Auth.auth().currentUser?.uid == nil{
print("Logout call")
perform(#selector(handleLogout), with: nil, afterDelay: 0)
}else{
print("User Logged In")
fethUserAndSetupNavbarTitle()
}
following code will get data from firebase realtime database.
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let dbReference = Database.database().reference().child("Users").child(uid)
dbReference.observeSingleEvent(of: .value, with: { (snapshot)
in
if let dictionary = snapshot.value as? [String:AnyObject] {
let user = Users(dictionary: dictionary)
print("setup navbar called")
self.setupNavBarWithUser(user)
}
}, withCancel: { (error) in
print(error.localizedDescription)
return
})
Please let me know if i am unable to describe the problem.
Thank you in advance.
I might know what the problem is.
The Firebase Token gets disabled after one hour or so.
You need to enable Token Service API to allow refreshing the token once it expires.
Go to https://console.developers.google.com/ , click onto "Enable APIs And Services", search for Token Service API and enable it.

Firebase iOS user authentication: avoiding getting logged out of app

Throughout my app, I am using two ways of getting the current userID at any given time. Both of them I have picked up somewhere and (as far as I can tell) work largely fine. And then I use Method 3 as a variation of Method 2.
Method 1:
if let user = Auth.auth().currentUser{
let uid = user.uid
}
Method 2:
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// User is signed in.
self.USER = user
self.userID = self.USER?.uid
} else {
// No user is signed in.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "LoginScreen")
self.present(vc!, animated: true, completion: nil)
}
Method 3:
handle = Auth.auth().addStateDidChangeListener { (auth, user) in
if let user = user {
// User is signed in.
self.USER = user
self.userID = self.USER?.uid
} else {
// No user is signed in.
}
Now it seems that Method 1 and 3 work largely equivalently, while Method 2 sends me back to the Login screen more often (e.g. when the phone goes from 3G to Wifi or flight mode).
Given that I would like my app to stay logged in for long (even when going to the background and coming back) that would suggest using Method 1 or 3. However, I don't quite understand
the difference between Methods 1 and 3
what is the app supposed to be doing when 1 and 3 cannot establish a connection? Is it supposed to freeze until a connection is re-established? I worry Methods 1 and 3 might be more prone to crashes. But Method 2 looks out annoyingly often.
Generally, once a user has authenticated correctly with Firebase, is there ever a reason for the app to go back to a Login screen? Can the user not stay logged in for arbitrary periods of time (as e.g. a Facebook would)? If so, with which method can I achieve that?
Just picking up on a couple of points you have raised first, you mention the app freezing when method 2 and 3 are called. Where are you calling these? You need to ensure that these are run on the main thread so that they don't interfere with your UI given it's an asynchronous function.
Also, are you removing the state change listener once logged in? Within your login VC you could have:
deinit {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
Furthermore, you can use the GIDSignIn.sharedInstance().signInSilently() method.
Take a look at the iOS friendly chat sample, they have a good login flow and handle the user already being logged in quite well. https://codelabs.developers.google.com/codelabs/firebase-ios-swift/#0

Checking Firebase current signed-in user via Listener in iOS

I've implement Firebase Authorization to login on my iOS app via Facebook and Google. I'm coding Swift.
When the app starts up I need to check whether a user is already signed-in in order to present the proper ViewController (e.g., if nobody is signed in I present the Login View Controller, otherwise I present the Home View Controller).
If I use the "easy" solution offered by Firebase, meaning
if FIRAuth.auth()?.currentUser != nil {
// User is signed in.
// ...
} else {
// No user is signed in.
// ...
}
So checking if the current user is not nil, it happens exactly what the Firebase guide (https://firebase.google.com/docs/auth/ios/manage-users) alerts might happen meaning
"Note: currentUser might also be nil because the auth object has not finished initializing. If you use a listener to keep track of the user's sign-in status, you don't need to handle this case."
So I would like to implement the listener as suggested in the guide:
handle = FIRAuth.auth()?.addStateDidChangeListener() { (auth, user) in
// ...
}
The listener will handle also intermediate status so that it is triggered when the Auth object is created. Point is I really cannot make it to work properly. Anybody can help me to use this listener in order to check if a user is logged in?
Thanks
I've implemented it like this:
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if let user = user {
// User is signed in. Show home screen
} else {
// No User is signed in. Show user the login screen
}
}
If you don't need the User object after checking, you can replace if let user = user with a boolean test, like this:
FIRAuth.auth()?.addStateDidChangeListener { auth, user in
if user != nil {
// User is signed in. Show home screen
} else {
// No User is signed in. Show user the login screen
}
}
Where to put the listener (from the comments):
For the cases I used to check if a user is signed in, it was enough to put it at the beginning of viewDidLoad in the specific view controller. But if you have any cases where you need to check every time you enter the specific view controller then it would be better to put it at the beginning of viewDidAppear. But I think in most cases you need to check only once, if the user enters the view
If you're setting up the StateDidChangeListener in application:didFinishLaunchingWithOptions, you'll notice that it fires once when the listener is attached (to set the initial state, which is nil when initialising) and then again once it's finished initialising (potentially not nil). This is intended behaviour, but really impractical if you're setting it up early.
An alternative to using the listener is using NotificationCenter. This will fire once initialisation has finished:
NotificationCenter.default.addObserver(forName: NSNotification.Name.AuthStateDidChange, object: Auth.auth(), queue: nil) { _ in
let user = Auth.auth().currentUser
}

Resources