How to Structure Data Under Saved Data in Firebase - ios

This is my code in Firebase.
//Create a reference to a Firebase database URL
Firebase *savedRef2 = [[Firebase alloc] initWithUrl:#"https://example.firebaseio.com/Location_Coordinates"];
// Write data to Firebase
Firebase *postRef2 = [savedRef2 childByAppendingPath: #"LOCATION DATA"];
NSDictionary *post1 = #{
#"DATE": dateString,
#"Lat": #(location.coordinate.latitude),
#"Long": #(location.coordinate.longitude),
#"USER": name,
};
Firebase *post1Ref = [postRef2 childByAutoId];
[post1Ref setValue: post1];
When a user clicks save on the app, the data is saved to my Firebase data base but it keeps saving as a new object right under the previous saved. How can I make it so it saves once and the rest that are saved are childs of that first saved data?
This is a picture Current saved data for coordinates. of how it keeps saving and I don't want it like this. How can I make it so it looks like this 2. I am saving coordinates of users but it keeps recreating a new 'childByAutoId' string and I just want the saved data to fall under the 1st 'childByAutoId' string and not to keep creating new ID's so each users coordinates fall under one ID each time it is saved.
UPDATE:
I have different users submitting data and in that data are coordinates. I want that same users data to be created ONLY under ONE node using 'childByAutoID' and the rest of the data (coordinates) will be under that. The way it is set up right now is that MULTIPLE nodes (childByAutoId) are being logged to Firebase and it makes it hard for me to read it so that is why I want ONE node (childByAutoId) created first and the rest fall under that EACH time someone clicks save.
This is how I currently have it. And this is how I want it

Super easy!
When a user authenticates the auth Firebase variable is populated with their user id.
In general if you are storing information about users you would store that data or associate that data with their uid.
So, when you want to store data for each user, store it in a node where the parent is their uid
Location_Coordinates
LOCATION DATA
uid_0
childByAutoId
childByAutoId
uid_1
childByAutoId
childByAutoId
then to create the reference
Firebase *rootRef = [[Firebase alloc] initWithUrl:#"https://example.firebaseio.com];
Firebase *locCoordsRef = [rootRef childByAppendingPath("Location_Coordinates");
Firebase *locDataRef = [locCoordsRef childByAppendingPath("LOCATION DATA"];
NSString *uid = rootRef.authData.uid
Firebase *thisUserRef = [locDataRef childByAppendingPath(uid)];
NSDictionary *userDataDict = #{
#"DATE": dateString,
#"Lat": #(location.coordinate.latitude),
#"Long": #(location.coordinate.longitude),
#"USER": name,
};
Firebase *dataRef = [thisUserRef childByAutoId];
[dataRef setValue: userDataDict];

Related

Firesbase authentication iOS login get user detail

I have just followed and correctly added the following tutorial to my app. But I’m struggling on how to get the ‘logged in’ users details from the Firestore database?
The Tutorial:
https://youtu.be/1HN7usMROt8
Once the user registers through my app the ‘First name’, ‘last name’ and ‘UID’ are saved by the Firestore database. But once the user logs in how do I GET the users first name to copy to a label on a welcome screen inside the app?
Thanks
I suppose that you know how Firestore works. If you don't you can get all informations in this documentation or watch videos about Firestore on YouTube.
So let's say you have your users saved in collection named users. When the user sign in you can get current user like this:
let currentSignInUser = Auth.auth().currentUser!
If you will force unwrap it make sure that user will be signed in.
When you have current user you can get his uid by calling uid property of current user:
let currentUserID = currentSignInUser.uid
Now you can query this uid to get documents from Firestore users collection that have same UID field value as your current signed in user:
let query = Firestore.firestore().collection("users").whereField("UID", isEqualTo: currentUserID)
Make sure that the first parameter of whereField() function is same as your field name in documents. Because you mention that UID is saved to database I name it UID.
This is all for getting informations of your current user and making query out of it. Now you can GET documents from Firestore:
query.getDocuments() { [weak self] (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let userInfo = querySnapshot?.documents.first {
DispatchQueue.main.async {
// Set label text to first name
self?.youLabel.text = userInfo["firstName"]
}
}
}
}
Change userInfo key to whatever name have you used for storing first name in Firestore. For example if you named your document field firstName then userInfo dictionary key must be firstName.

Given a users UID can I access his profile info?

I know that I can save user info to his profile with firebase. But I am wondering if I am able to get this same info when all I have is a UID?
As I understand it I can only do this to get the info:
Auth.auth().currentUser.displayName...
It seems like If I want to be able to fetch other users info I would have to keep a copy of their data in a users node.
Am I correct? Or can I access their profile info withought having to keep a copy?
If you use firebase authentication, then you can retrieve the current user info. So, if currently user X is logged in and he is authenticated, you can retrieve the following info about him:
let user = Auth.auth().currentUser
if let user = user {
// The user's ID, unique to the Firebase project.
// Do NOT use this value to authenticate with your backend server,
// if you have one. Use getTokenWithCompletion:completion: instead.
let uid = user.uid
let email = user.email
let photoURL = user.photoURL
// ...
}
If you want to retrieve information about other users, then in that case you need to use firebase database:
users
userId (firebase authentication)
name : user X
userId
name : user Y
Depending on the reason you need this data, you can actually get access to an auth user by using the firebase-admin package.
inside of your firebase function - or node.js backend - (this is only for backend node.js functions, not for front-end), you can get the user by doing the following:
const admin = require("firebase-admin");
const authUser = await admin.auth().getUser(uid);
authUser = authUser.toJSON(); // Optional
You can read more about retrieving user data here
If you want to load in multiple users at once, you can easily use the getUsers function and it's also possible to load in the user based on other information like email:
getUser(uid)
getUserByEmail(email)
getUserByPhoneNumber(phoneNumber)

How to sync contacts with server contacts in shortest iteration and faster?

I have created one application the same as WhatsApp to chat with the same industry people and my basic concept is to sync user contact and find a user who is using this app and users can chat with each other.
Contact sync I have done in my application and its working fine till 100 to 500 contacts but if any user has 2000 to 3000 contacts in his contact book its taking time to sync with the server.
I am using the below code to get user contacts and sent them to the server.
ABAddressBookRef addressBookRef = ABAddressBookCreateWithOptions(NULL, nil);
NSArray *allContacts = (__bridge NSArray *)ABAddressBookCopyArrayOfAllPeople(addressBookRef);
NSMutableArray *contacListAddIntoDatabase = [NSMutableArray new];
for (id record in allContacts) {
ABRecordRef thisContact = (__bridge ABRecordRef)record;
//save Contact in DataBase.
}
After saving all records in the local database i am sending that record to the server to check if that contact is a user of my app and get backlist of users who are using my app. I have implemented paging in contactsync service.
Here is my contact sync to server code :
if ([[APP_DELEGATE contactArrayForSync] count] > 0) {
int long pageSize = ([[APP_DELEGATE contactArrayForSync] count] > CONTACT_SYNC_PAGE_SIZE ) ? CONTACT_SYNC_PAGE_SIZE : [[APP_DELEGATE contactArrayForSync] count];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:contactList
options:0
error:&error];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[[APP_DELEGATE apiManager] syncUserContacts:[APP_DELEGATE getEncryptedUserID] andJSONString:jsonString withCallback:^(BOOL success, id responseObject) {
//Update DataBase as per server response
}];
}
to sync 2000 contacts it's taking around 2min to sync and its very large amount of time to sync contacts.
I want to reduce this time so is there any way to get sync data in sorter time.
I am stuck here and the client asking a faster solution to sync my contacts.
Note: In the server, I have around 50000 records in my Contact table and it may increase in the future.
any suggestions will be appreciated.
UPDATE: At the first time I am sending all contacts to a server in paging that's fine but after first sync every second sync I need to send only updated or newly added records right now I am sending all record to the server every time.
so how can I get an updated record or newly added record and faster next time sync also?
I think you can speed up the syncing by changing some implementation to your code as well on server side code. you send all phone numbers in on array of dictionary (name, all phone number comma separated) you need to create this structure while you are fetching the records from address book, now change your logic accordingly on the server side. both of your task storing in database and syncing can be done on different threads.
Hope this helps for you.
You can use realm for local storage with observe function. Then you can listen changes from realm for remote sync.

Is is normal to end up with a LOT of changes on CKFetchRecordChangesOperation?

I use CloudKit in my app, which seems to be working well. However when I initialise the app on a new device, using
CKFetchRecordChangesOperation *fetchRecordChangesOperation = [[CKFetchRecordChangesOperation alloc] initWithRecordZoneID:zoneID previousServerChangeToken:NIL];
I get a LOT of changes, as all previous deletions and changes appear to be synched across.
Is there a better way? for example to just download a full set of current data and set the serverChangeToken to the current value.
When looking at the documentation of the CKServerChangeToken it looks like you can only get it when using the CKFetchRecordChangesOperation. See: https://developer.apple.com/library/prerelease/ios/documentation/CloudKit/Reference/CKServerChangeToken_class/index.html
So now how could you get a hold on that token:
Usually you will do something like this: As you can see in the CKFetchRecordChangesOperation you can initialize it with a previousServerChangeToken. This token works like a timestamp. When the operation completes, you get this token back in the fetchRecordChangesCompletionBlock. You have to save that token in for instance the user defaults. Then the next time you start a CKFetchRecordChangesOperation, you can use that token to start reading the changes since the last time you called it.
Actually saving the token can be a little tricky. I can suggest adding a property like this:
private var previousChangeToken: CKServerChangeToken? {
get {
let encodedObjectData = NSUserDefaults.standardUserDefaults().objectForKey("\(container.containerIdentifier)_lastFetchNotificationId") as? NSData
var decodedData: CKServerChangeToken? = nil
if encodedObjectData != nil {
decodedData = NSKeyedUnarchiver.unarchiveObjectWithData(encodedObjectData!) as? CKServerChangeToken
}
return decodedData
}
set(newToken) {
if newToken != nil {
NSUserDefaults.standardUserDefaults().setObject(NSKeyedArchiver.archivedDataWithRootObject(newToken!), forKey:"\(container.containerIdentifier)_lastFetchNotificationId")
}
}
}
In your case you want a new application to start with a change token that could only have been created by an other running app. So besides saving the change token to the NSUserDefaults, you should also save it in CloudKit in the public Database in one specific settings recordType record. A newly installed app that does not have a token in it's NSUserDefaults can then read the token from your CloudKit settings record.

error CloudKit access was denied by user settings when user is not sign in

I'm trying to get fetch the notification of changes in the public database:
CKServerChangeToken *token = [[cloudKitToken helper] getCloudKitToken];
CKFetchNotificationChangesOperation *op = [[CKFetchNotificationChangesOperation alloc]
initWithPreviousServerChangeToken:token];
NSMutableArray *noteIds = [[NSMutableArray alloc] init];
op.notificationChangedBlock = ^(CKNotification *note) {
CKNotificationID *noteId = note.notificationID;
[noteIds addObject:noteId];
};
op.fetchNotificationChangesCompletionBlock = ^(CKServerChangeToken *token, NSError *error) {
NSLog(#"error %#", error.localizedDescription);
};
[[CKContainer defaultContainer] addOperation:op];
But when the user has not login to iCloud I get this error: "error CloudKit access was denied by user settings"
By another hand with the same device I can fetch records from the public database with no problem.
What I'm trying to do with the CKFetchNotificationChangesOperation is get the deltas between device and cloudkit.
Any of you knows how can I get the deltas when the user has not login to iCloud?, does fetch records is my only option for this case?
I'll really appreciate your help.
With CKFetchNotificationChangesOperation you can retrieve all the notifications that were send to you since a previous fetch. These notifications are linked to subscriptions that you previously created for a device. Subscriptions are linked to an account. So you must be logged in with the same account in order to fetch these notifications.
In your case you might be better of by using CKFetchRecordChangesOperation. Then you would be able to just query for CloudKit changes since a previous query. Since it's just a query using a 'server change token'. The first time you can query it with nil and then retrieve all the records. you would get a 'server change token' in return. just save that on the device and the next time you start the app just use that token. Then there would be no need to log in to iCloud. (if your data is public)

Resources