I am working on a chatting application, where i have to maintain user "offline" or "online" status.I want the user status will be offline when user enters in background and also if user will kill the app.In my app in the background case it is working fine. but it is not working fine when i kill the app.My code is: -
func applicationDidEnterBackground(_ application: UIApplication) {
if let uid: Int = UserDefaults.standard.value(forKey: "User_Id") as? Int{
reference(.User).whereField(kREGISTERUSEID, isEqualTo: "\(uid)").getDocuments { (snapshot, err) in
if let err = err {
}
else {
let document = snapshot!.documents.first
document!.reference.updateData([
kISONLINE: false
])
}
}
}
}
func applicationDidBecomeActive(_ application: UIApplication) {
if let uid: Int = UserDefaults.standard.value(forKey: "User_Id") as? Int{
reference(.User).whereField(kREGISTERUSEID, isEqualTo: "\(uid)").getDocuments { (snapshot, err) in
if let err = err {
}
else {
let document = snapshot!.documents.first
document!.reference.updateData([
kISONLINE: true
])
}
}
}
}
func applicationWillTerminate(_ application: UIApplication) {
if let uid: Int = UserDefaults.standard.value(forKey: "User_Id") as? Int{
reference(.User).whereField(kREGISTERUSEID, isEqualTo: "\(uid)").getDocuments { (snapshot, err) in
if let err = err {
}
else {
let document = snapshot!.documents.first
document!.reference.updateData([
kISONLINE: false
])
}
}
}
}
I am using firebase here.Also i want to make user offline if user will not go to the chat screen or will not send message to any one (like whatsapp) for 20 seconds .
Please help me to implement it. Thanks
Your api or firebase call must be performed on the background mode. Go the link you will find how to crate a background task.
Can I make an api call when the user terminates the app?
Related
#Published var isNewUser: Bool?
init() {
self.isNewUser = false
}
func checkIfTheUserExistsInDataBase(
userID: String?, completion: #escaping (_ isNewuser: Bool) -> Void
) {
let docRef = db.collection("users").whereField("user_id", isEqualTo: userID!).limit(to: 1)
docRef.getDocuments { querySnapshot, error in
if error != nil {
print(error?.localizedDescription)
} else {
if let doc = querySnapshot?.documents, doc.isEmpty {
completion(true)
} else {
completion(false)
}
}
}
}
func login(
email: String, password: String,
completion: #escaping (_ error: Error?, _ isEmailVerified: Bool) -> Void
) {
Auth.auth().signIn(withEmail: email, password: password) { authDataResult, error in
if error == nil {
if authDataResult!.user.isEmailVerified {
DispatchQueue.main.async {
self.checkIfTheUserExistsInDataBase(userID: authDataResult?.user.uid) { isNewUser in
self.isNewUser = isNewUser
}
}
UserDefaults.standard.set(authDataResult?.user.uid, forKey: CurrentUserDefaults.userID)
completion(error, true)
} else {
print("Email not verified")
completion(error, false)
}
} else {
completion(error, false)
}
}
}
I tried to use DispatchSemaphore to let a longer running function execute first which is checkIfTheUserExistsInDataBase, but it froze my app. Is there a better way to do this?
Firebase supports async/await (see this short, this video, and this blog post I created to explain this in detail.
To answer your question: you should use async/await to call signing in the user, waiting for the result, checking if the user exists in your Firestore collection, and the updating the UI.
The following code snippet (which is based on this sample app) uses the new COUNT feature in Firestore to count the number of documents in the users collection to determine if there is at least one user with the ID of the user that has just signed in.
func isNewUser(_ user: User) async -> Bool {
let userId = user.uid
let db = Firestore.firestore()
let collection = db.collection("users")
let query = collection.whereField("userId", isEqualTo: userId)
let countQuery = query.count
do {
let snapshot = try await countQuery.getAggregation(source: .server)
return snapshot.count.intValue >= 0
}
catch {
print(error)
return false
}
}
func signInWithEmailPassword() async -> Bool {
authenticationState = .authenticating
do {
let authResult = try await Auth.auth().signIn(withEmail: self.email, password: self.password)
if await isNewUser(authResult.user) {
}
return true
}
catch {
print(error)
errorMessage = error.localizedDescription
authenticationState = .unauthenticated
return false
}
}
See this video for more details about how to implement Firebase Authentication in SwiftUI apps.
I'm setting up a shareExtension in iOS and want to use the FirebaseSDK to upload data direct instead of using AppGroups. This works as expected, but after 1 hour the UserToken get's invalidated and i can't reach the Firestore Backend anymore.
I'm using the FirebaseSDK (6.2.0) and enabled Keychain sharing to access the current signedIn User. I have the same Google-Plist in the MainApp and the shareExtension. The data gets also uploaded correctly from the shareExtension and was also updated via the snapshotListener in the MainApp.
Relevant code in the MainApp
lazy var db = Firestore.firestore()
//TEAMID form the Apple Developer Portal
let accessGroup = "TEAMID.de.debug.fireAuthExample"
override func viewDidLoad() {
super.viewDidLoad()
do {
try Auth.auth().useUserAccessGroup("\(accessGroup)")
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
guard let user = Auth.auth().currentUser else {
self.statusLabel.text = "user get's lost"
return
}
statusLabel.text = "UserID: \(user.uid)"
// Do any additional setup after loading the view.
db.collection("DummyCollection").addSnapshotListener { (querySnapshot, error) in
if let err = error {
print(err.localizedDescription)
}
guard let snapshot = querySnapshot else {
return
}
DispatchQueue.main.async {
self.dbCountLabel.text = "\(snapshot.count)"
}
}
}
func signIN(){
// https://firebase.google.com/docs/auth/ios/single-sign-on
do {
try Auth.auth().useUserAccessGroup("\(accessGroup)")
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
Auth.auth().signInAnonymously { (result, error) in
if let err = error{
print(err.localizedDescription)
return
}
print("UserID: \(Auth.auth().currentUser!.uid)")
}
}
}
}
Code in the shareExtension:
override func viewDidLoad() {
super.viewDidLoad()
if FirebaseApp.app() == nil {
FirebaseApp.configure()
}
do {
try Auth.auth().useUserAccessGroup(accessGroup)
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
tempUser = Auth.auth().currentUser
if tempUser != nil {
userIDLabel.text = "UserID: \(tempUser!.uid)"
doneButton.isEnabled = true
db.collection("DummyCollection").addSnapshotListener { (querySnapshot, error) in
if let err = error {
print(err.localizedDescription)
}
guard let snapshot = querySnapshot else {
return
}
DispatchQueue.main.async {
self.dataCountLabel.text = "\(snapshot.count)"
}
}
} else {
// No user exists in the access group
self.navigationItem.title = "No User"
}
}
I expect that this should be possible, but the Token gets somehow invalid in the MainApp and i could not reach the Firestore backend.
6.2.0 - [Firebase/Auth][I-AUT000003] Token auto-refresh re-scheduled in 01:00 because of error on previous refresh attempt.
6.2.0 - [Firebase/Firestore][I-FST000001] Could not reach Cloud Firestore backend. Connection failed 1 times. Most recent error: An internal error has occurred, print and inspect the error details for more information.
Answering my own question: This should be fixed in the next release (Firebase 6.4.0) Details can be found in this PR 3239.
I'm using firebase firestore and authentication .
My app is basically managing orders, when a user sends a new order to firestore it gets a openOrder default Boolean var, I have another app that manage this order and once my other app reads the order the boolean var changes value.
All of that works.
My issue is when a user closes completly the app and then reopens it I need to check if the openOrder is true or not and according to that set my rootViewController .
I'm using a completion handler to fetch the openOrder var and check if it is true or not but applicationDidFinishLaunchingWithOptions returns true before I set my local vars according to the firestore functions.
my code is :
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
let reValue = loadPriviousDishesIfUserQuitsAppBeforeClosingTab(completion: { success in
guard success! else { return }
//here I have all the values I need and need to return only here
})
return true
}
func loadPriviousDishesIfUserQuitsAppBeforeClosingTab(completion: #escaping (Bool?) ->()) {
db = Firestore.firestore()
let myUserID = Auth.auth().currentUser?.uid
db.collection("users").whereField("user", isEqualTo: myUserID!).whereField("openOrder", isEqualTo: true).getDocuments { (querySnapshot, error) in
if let err = error {
print(err.localizedDescription)
completion(nil)
}
else {
for doc in (querySnapshot?.documents)! {
guard let restID = doc.data()[ResttId"]
else {return}
myRestaurant.restID = restID as? String
self.setMyRestMenu(completion: { success in
guard success else { return }
//here I set all my values using fetching all the data from firestore,
})
}
completion(true)
}
}
}
You can show a loading activity above the rootViewController until get that value , then
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let reValue = loadPriviousDishesIfUserQuitsAppBeforeClosingTab(completion: { success in
guard success! else { return }
//here I have all the values I need and need to return only here
let stor = UIStoryboard.init(name: "Main", bundle: nil)
let welcomeView = stor.instantiateViewController(withIdentifier: "orderView")
let nav = UINavigationController(rootViewController: welcomeView )
nav.navigationBar.isHidden = true
self.window?.rootViewController = nav
})
return true
}
Edit : set storyboardID here
I am building am application with a Firestore back end, and I am trying to call a document down with the current user's info for their settings page. I am able to do this no problems when updating an empty array, but am having a terrible time trying to populate a single document. I have a custom object called DxUser:
struct DxUser {
var email:String?
var name:String?
var timeStamp:Date?
var profileImageURL:String?
var ehr: String?
var dictionary:[String:Any]? {
return [
"email":email,
"name":name,
"timeStamp":timeStamp,
"profileImageURL":profileImageURL,
"ehr": ehr as Any
]
}
}
extension DxUser : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let email = dictionary["email"] as? String,
let name = dictionary["name"] as? String,
let timeStamp = dictionary["timeStamp"] as? Date,
let profileImageURL = dictionary["profileImageURL"] as? String,
let ehr = dictionary["ehr"] as? String else {return nil}
self.init(email: email, name: name, timeStamp: timeStamp, profileImageURL: profileImageURL, ehr: ehr)
}
}
In the view controller, I am trying to update this variable with the current user, but I can only grab it in the closure, and it populates as nil anywhere outside the block. Here is the basic code I am using, but can anyone tell me what I am missing?
class SettingsController: UIViewController {
var dxUser = DxUser()
override func viewDidLoad() {
super.viewDidLoad()
fetchUser()
}
func fetchUser() {
guard let uid = Auth.auth().currentUser?.uid else {
return
}
let userRef = db.collection("users").document(uid)
userRef.getDocument { (document, error) in
if error != nil {
print(error as Any)
} else {
self.dxUser = DxUser(dictionary: (document?.data())!)!
self.navigationItem.title = self.dxUser.name
print (self.dxUser)
}
}
}
Yeah, this is how I am doing it on the table view, but I didn't see anything comparable on the regular VC.
db.collection("users").document(uid!).collection("goals").getDocuments { (snapshot, error) in
if error != nil {
print(error as Any)
} else {
//set the profile array to equal whatever I am querying below
goalsArray = snapshot!.documents.flatMap({Goal(dictionary: $0.data())})
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
}
It's not so much about the location of where you can access dxUser. It's the timing. Firebase APIs are asynchronous, which means userRef.getDocument() returns immediately, and you receive a callback only after the request completes. If you try to access dxUser right after that call within your fetchUser() method, it will not be available, because the request isn't complete. Given that's how async APIs work, you should only work with dxUser after the callback invoked, which usually means delaying the rendering of that data in your app (such as where your print statement is).
Please read more here about why Firebase APIs are asynchronous and what you can expect from them.
I actually figured it out. I pulled the fetchUser() function out to the viewWillAppear function so I called it before the view appeared. Then I did the async function to re-run viewDidLoad. Anyone else have any better suggestions?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
fetchUser()
}
func fetchUser() {
let userRef = db.collection("users").document(uid)
userRef.getDocument { (document, error) in
if error != nil {
print(error as Any)
} else {
self.dxUser = DxUser(dictionary: (document?.data())!)!
DispatchQueue.main.async {
self.viewDidLoad()
}
self.navigationItem.title = self.dxUser.name
}
}
}
I've been struggling with this issue for the better part of five days now and am unable to come up with a solution. Im hoping you all will be able to help me out. :) I am currently in my 10th week of a coding bootcamp so please excuse any mistakes I make in my terminology. Thank you.
So... Here is my problem. A user creates an event and that event send a notification to all the user of the app. ( V.2. will use location to only send it to those near you ) Once you receive that push notification i want the app to open a specific view and populate the fields with the information from the User that created the event. let me show you my code.
// Youre CRUD Methods go here.
func createEvent(eventSummary: String, eventLongtitude: Float, eventLatitude: Float, eventCreationDate: NSDate, completion: (() -> Void)?) {
guard let currentUser = UserController.sharedInstance.currentUser else { return }
let event = Event(creator: currentUser, eventCreationDate: eventCreationDate, eventLatitude: eventLatitude, eventLongtitude: eventLongtitude, eventSummary: eventSummary)
saveContext()
if let completion = completion {
completion()
}
if let eventRecord = event.cloudKitRecord {
cloudKitManager.saveRecord(eventRecord, completion: { (record, error) in
if let record = record {
event.update(record)
}
})
}
}
func retrieveEventForRecordID(recordID: CKRecordID, completion: (Event) -> Void) {
self.cloudKitManager.fetchRecordWithID(recordID) { (record, error) in
if let error = error {
NSLog("Error fetching event for record ID \(recordID): \(error)")
return
}
guard let record = record else { return }
if let event = Event(record: record) {
completion(event)
}
}
}
This shows my createEvent method that should take in the user that created the event.
func userWithName(userRecordID: CKRecordID, completion: ((user: User?) -> Void)?) {
cloudKitManager.fetchRecordWithID(userRecordID) { (record, error) in
if let record = record {
if let user = User(record: record) {
if let completion = completion {
completion(user: user)
}
}
} else {
if let completion = completion {
completion(user: nil)
}
}
}
}
And this is whats in my App delegate.
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
guard let remoteNotificationDictionary = userInfo as? [String: NSObject] else { return }
let cloudKitNotification = CKQueryNotification(fromRemoteNotificationDictionary: remoteNotificationDictionary)
guard let recordID = cloudKitNotification.recordID else { return }
let eventController = EventController.sharedInstance
eventController.retrieveEventForRecordID(recordID) { (event) in
// do whatever with this event
dispatch_async(dispatch_get_main_queue()) {
// show table view
let eventAlert = UILocalNotification()
eventAlert.alertTitle = "Place holder for title"
eventAlert.alertBody = "Place for body" // This is the local. This is what the user will see.
application.presentLocalNotificationNow(eventAlert)
print("Alert has been sent... maybe... prolly not." )
// TODO: alert to show users that there was an event.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let destinationViewController = storyboard.instantiateViewControllerWithIdentifier("EventNotifVC") as? EventNotificationTableViewController else { return }
destinationViewController.modalPresentationStyle = .FullScreen
destinationViewController.event = event
if let rootVC = self.window?.rootViewController {
rootVC.presentViewController(destinationViewController, animated: true, completion: nil)
}
}
}
}
I believe that with those code snippets it should show my line of thinking. Create and event with a user. -> make it the creator -> and with that creator populate fields... Im so lost with this... Any help would be appreciated.
Also, this is how I'm setting the label..
func updateViews() {
guard let event = event,
creator = event.creator else {
// Update views to be blank?
return
}
userEventSummaryTextView.text = event.eventSummary
usernameLabel.text = creator.username
userPhoneNumber.text = creator.phoneNumber
}
override func viewDidLoad() {
super.viewDidLoad()
updateViews()
}