Deleting Realm Object entry if its deleted in API Response - ios

First response in this I get two user i.e abc#gmail.com & xyz#gmail.com
[{
"email": "abc#gmail.com",
"type": "primary_email",
"linked_to": {
"_id": "DAS44564dasdDASd",
"image": null,
"company": null,
"designation": null,
"name": null
},
"active_platforms": [
"asd",
"qwe"
]
},
{
"email": "xyz#gmail.com",
"type": "primary_email",
"linked_to": {
"_id": "DAS44564dasdDASd",
"image": null,
"company": null,
"designation": null,
"name": null
},
"active_platforms": [
"asd",
"qwe"
]
}]
Now if abc#gmail.com is deleted if I do API call again then still I get abc#gmail.com in my object as it is not deleted from my realm. So how to handle such situation ?
// write request result to realm database
let entries = json["data"]
realm.beginWrite()
for (_, subJson) : (String, JSON) in entries {
let entry: AppUsers = Mapper<AppUsers>().map(JSONObject: subJson.dictionaryObject!)!
realm.add(entry, update: true)
}
do {
try realm.commitWrite()
} catch {
}

Update your logic as below. This is one of method to this.
Add one extra bool field to AppUsers model say 'active'. Update your code as below
// write request result to realm database
let entries = json["data"]
realm.beginWrite()
//Fetch all realm AppUsers objects
let allAppUsers = //TODO fetch all AppUsers objects here
for user in allAppUsers {
user.active = false
}
for (_, subJson) : (String, JSON) in entries {
let entry: AppUsers = Mapper<AppUsers>().map(JSONObject: subJson.dictionaryObject!)!
entry.active = true
realm.add(entry, update: true)
}
for user in allAppUsers {
if !user.active {
realm.delete(user)
}
}
do {
try realm.commitWrite()
} catch {
}

This sounds like an issue where the data in your Realm database has become stale as the contents no longer match what is on the server.
The Realm API realm.add(_, update: true) will update any objects that were passed to it, but simply not passing an object does not imply it should be deleted (More that you just didn't want to update it).
There's no way for Realm to automatically know if an object needs to be deleted. You'll need to be in charge of that logic yourself.
Since your mechanism for checking if an object is deleted is via its email address, you could capture the email addresses of each object you've updated, and then delete any other objects whose email address is not in there.
// write request result to realm database
realm.beginWrite()
let entries = json["data"]
var updatedEmails = [String]()
for (_, subJson) : (String, JSON) in entries {
let entry: AppUsers = Mapper<AppUsers>().map(JSONObject: subJson.dictionaryObject!)!
// Save the email we just processed
updatedEmails.append(entry.email)
realm.add(entry, update: true)
}
// Delete all objects not in the updated emails list
let realmEntries = realm.objects(AppUsers.self)
for entry in realmEntries {
if !updatedEmails.contains(entry.email) {
realm.delete(entry)
}
}
do {
try realm.commitWrite()
} catch {
}
If your REST API brings down all of your objects in their complete form each time, a much quicker solution would also be to simply empty the Realm file each time and just add the objects as new objects each time too.

Related

Why Realm stores values in wrong order?

I have a response, I'm storing that response in realm database,
I got below response but it save in the incorrect order.
It saves in realm in like below,
“appData3”
“appData8”
“appData4”
“appData1”
“appData7”
“appData1”
“appData6”
“appData5”
response is like below
{
"data": {
"APP_DATA_1": {
"vValue": “appData1”
},
"APP_DATA_2": {
"vValue":“appData2”
},
"APP_DATA_3": {
"vValue": “appData3”
},
"APP_DATA_4": {
"vValue":“appData4”
},
"APP_DATA_5": {
"vValue": “appData5”
},
"APP_DATA_6": {
"vValue": “appData6”
},
"APP_DATA_7": {
"vValue": “appData7”
},
"APP_DATA_8": {
"vValue": “appData8”
}
}
}
I store value using below code
DataManager.shared.store(from: data.data.appData1)
DataManager.shared.store(from: data.data.appData2)
DataManager.shared.store(from: data.data.appData3)
DataManager.shared.store(from: data.data.appData4)
DataManager.shared.store(from: data.data.appData5)
DataManager.shared.store(from: data.data.appData6)
DataManager.shared.store(from: data.data.appData7)
DataManager.shared.store(from: data.data.appData8)
func store<T>(from object: T) where T: Object {
realmMainDB.beginWrite()
realmMainDB.add(object, update: .all)
try! realmMainDB.commitWrite()
}
if you want any related code let me,
I appreciate another method or way to store that response.
or let me know if How can I can store that response in user defaults.
thank you
Realm Results objects do not have a guaranteed order so whether saving or retrieving you should not expect results to be in an kind of order.
List objects on the other hand do guarantee their order.
Of you want to order a Results object, include a property that ensures that ordering, and when retrieving the results, always sorted(byKeyPath: that field.
Ordering with a timestamp or date string is popular but it will depend on the use case.
A datestring can be in a yyyymmdd format, so update your models to include that propery
{
"data": {
"APP_DATA_1": {
"vValue": “appData1”
"datestring": "20210625"
},
"APP_DATA_2": {
"vValue":“appData2”
"datestring": "20210525"
},
when when reading
let results = realm.objects(DataClass.self).sorted(byKeyPath: "datestring", ascending: true)
The other advantage of this is when new data is added, it will be reflected in the results in the correct order.

Successful Firestore Write Not Reflected in Data

I am expecting data for a document field to be updated via a setData (merge) write and it does not despite completing successfully. An updateData works, but I don't understand why setData doesn't work in this instance.
I am performing the following writes to Firestore and the following paths:
setData to firstCollection/uniqueDoc
batched setData to secondCollection/uniqueDoc1 (between 1-5 writes in batch)
transaction updateData to thirdCollection/uniqueDoc
No error prints out, all data and syntax appear correct, and #2 plus #3 writes reflect in the data successfully - #1 is the one that does not.
Any guidance would be appreciated.
Here's my code for #1...
func first() {
//oldData is not empty and is the same type as var updatedData
//oldData = [
// "a": ["name":"aName", "dateAdded": dateValue, "imageUrl": "aImageUrl"]
// "b": ["name":"bName", "dateAdded": dateValue, "imageUrl": "bImageUrl"]
// ]
guard let oldData = oldData else { return }
var updatedData: [String: NSDictionary] = [:]
updatedData = oldData
updatedData["a"] = nil
db.collection(firstCollection).document(uniqueDocName).setData(["data": updatedData],
merge: true) { error in
guard error == nil else {
print("Error:" , error)
return
}
self.runOtherFunc()
}
}

How to fetch nested data from Firebase realtime database by implementing observer in swift

Firebase data-structure
lastLocations ( batteryStatus, lat, long, timestamp, uid)
profiles (name, phoneNumber, picture, uid)
userFriends ( basis on the uid -> how many friends -> conversationUid, friendStatus, notify, phoneNumber, uid)
My Code:
I have already created tableview and xib for it.
I have created model for last location, profiles, userFriends.
I already fetched the friend list but on Observe .ChildAdded
My uid zzV6DQSXUyUkPHgENDbZ9EjXVBj2
Link: https://drive.google.com/file/d/19cnkY03MXjrTFgzzPCdvmOuosDRvoMx9/view?usp=sharing
Issues:
Not getting an idea how to fetch location & profile with friend list efficient way with observer so any change come it reflects. Firebase is asyncrohonous process.
observer implementation so data not completely load every-time
Results to achieve:
I need to show friends list ( name, profile picture, battery status, lat long(address), timeStamp ) on tableview on the basis of my uid.
Firebase JSON
{
"lastLocations": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"batteryStatus": 22,
"latitude": 40.9910537,
"longitude": 29.020425,
"timeStamp": 1556568633477,
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
},
"zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
"batteryStatus": 88,
"latitude": 41.0173995,
"longitude": 29.1406086,
"timeStamp": 1571778174360,
"uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
}
},
"profiles": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
"name": "Skander",
"phoneNumber": "+95644125503",
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
},
"zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
"fcmToken": "enMneewiGgg:APA91bHyA4HypWUYhxGTUTTch8ZJ_6UUWhEIXRokmR-Y-MalwnrtV_zMsJ9p-sU_ZT4pVIvkmtJaCo7LFJYJ9ggfhc1f2HLcN9AoIevEBUqyoMN-HDzkweiUxAbyc84XSQPx7RZ1Xv",
"name": "Murad",
"phoneNumber": "+915377588674",
"picture": "profile/zzV6DQSXUyUkPHgENDbZ9EjXVBj2/a995c7f3-720f-45bf-ac58-b2df934e3dff.jpeg",
"uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
}
},
"userFriends": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
"conversationUid": "-L_w2yi8gh49GppDP3r5",
"friendStatus": "STATUS_ACCEPTED",
"notify": true,
"phoneNumber": "+915377588674",
"uid": "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
}
},
"zzV6DQSXUyUkPHgENDbZ9EjXVBj2": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"conversationUid": "-L_w2yi8gh49GppDP3r5",
"friendStatus": "STATUS_ACCEPTED",
"notify": true,
"phoneNumber": "+915644125503",
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1"
}
}
}
}
Swift Function:
func getFrndDataList(){
AppData.removeAll()
ref.child("userFriends").child("zzV6DQSXUyUkPHgENDbZ9EjXVBj2").observe(.childAdded, with: { (snapshot) in
guard let data = try? JSONSerialization.data(withJSONObject: snapshot.value as Any) else { return }
let frndList = try? JSONDecoder().decode(Friend.self, from: data)
self.AppData.append(frndList!)
self.tableView.reloadData()
print([frndList])
})
}
Note: After writing this answer I realized it was way long but this is a big question and there are a lot of elements to address.
My first suggestion is to change the structure as it's overly complicated for what's being done with the data. Also, there is repetitive data that's not needed so that should be changed as well. For example, here's your profiles node
"profiles": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
"name": "Skander",
"phoneNumber": "+95644125503",
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1" <- remove this, not needed.
},
As you can see, each child node has a key of the user id. But, you are also storing the user id as a child node as well. They key is the uid and will always be available so no need for duplication there and the child node should be removed.
Based on comments, this is a much better structure
/users
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1
"batteryStatus": 22,
"latitude": 40.9910537,
"longitude": 29.020425,
"timeStamp": 1556568633477,
"fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
"name": "Skander",
"phoneNumber": "+95644125503",
"conversationUid": "-L_w2yi8gh49GppDP3r5",
"friendStatus": "STATUS_ACCEPTED",
"notify": true,
"phoneNumber": "+915377588674",
and then, to keep track of a users friends, it becomes this
/userFriends
zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: true //their friend
IRoo0lbhaihioSSuFETngEEFEeoi: true //another friend
To load this users friends, we read the data at /userFriends/this_users_id and then iterate over the child nodes loading the data for display in the tableView
Lets start with an object that will be used to hold each friends data, and then an array that will be used as a tableView Datasource
class FriendClass {
var uid = ""
var name = ""
//var profilePic
//var batteryStatus
init(withSnapshot: DataSnapshot) {
self.uid = withSnapshot.key
self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
}
}
var myFriendsDataSource = [FriendClass]()
Then a functions to read the users node, iterate over the users friends uid's and read in each users data, populating the FriendClass object and storing each in an array. Note that self.ref points to my firebase.
func loadUsersFriends() {
let uid = "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
let myFriendsRef = self.ref.child("userFriends").child(uid)
myFriendsRef.observeSingleEvent(of: .value, with: { snapshot in
let uidArray = snapshot.children.allObjects as! [DataSnapshot]
for friendsUid in uidArray {
self.loadFriend(withUid: friendsUid.key)
}
})
}
func loadFriend(withUid: String) {
let thisUserRef = self.ref.child("users").child(withUid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let aFriend = FriendClass(withSnapshot: snapshot)
self.myFriendsDataSource.append(aFriend)
})
}
Now that we have the code to read in the data, you also want to watch for changes. There are a number of options but here's two.
1) I'll call this brute force.
Simply attach a .childChanged observer to the /users node and if something changes, that changed node is passed to the observer. If the key to that node matches a key in myFriendsDataSource array, update that user in the array. If no match, then ignore it.
func watchForChangesInMyFriends() {
let usersRef = self.ref.child("users")
usersRef.observe(.childChanged, with: { snapshot in
let key = snapshot.key
if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
let friend = self.myFriendsDataSource[friendIndex]
print("found user \(friend.name), updating")
//friend(updateWithSnapshot: snapshot) //leave this for you to code
}
})
}
2) Selective observing
For this, we simply attach an .childChanged observer to each friend node - and that can be done within the code example from above
func loadFriend(withUid: String) {
let thisUserRef = self.ref.child("users").child(withUid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let aFriend = FriendClass(withSnapshot: snapshot)
self.myFriendsDataSource.append(aFriend)
//add an observer to this friends node here.
})
}
One last thing: I didn't address this
"friendStatus": "STATUS_ACCEPTED",
I would think that only friends you accepted are in the friends list so the use is a tad unclear. However, if you want to use it you could do this
/userFriends
zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: "STATUS_ACCEPTED"
IRoo0lbhaihioSSuFETngEEFEeoi: "STATUS_DECLINED"
and then as you're itering over friends to load, ignore the ones that are declined.
If you MUST keep your current structure (which I do NOT recommend) the techniques in this answer will work for that structure as well, however, it will be a lot more code and you're going to be moving around a lot of unneeded extra data so the Firebase bill will be higher.

Understanding Firebase data binding with user identifiers

Guys I am trying to learn Firebase framework and I do have question about storing data, so as I understood, the data is stored as entire JSON file in the Firebase database.
Let's say I have few data models (just to simplify my description):
Users and Articles
Using Firebase SDK we can simple create user using very simple query. The Firebase user has uid which I can use for my purposes. My general question right now is how to bind uid with my data.
Correct me if I am wrong but I plan to use uid retrieved from the Firebase to create few nodes in Realtime DataBase like these:
{
"users": {
"$uid": {
"name": "Alex",
"lastname": "Matrosov"
},
"$uid": {
"name": "John",
"lastname": "Malkovich"
}
}
}
So when I create/sign in user I plan to add related info to the users/user node.
Then I want to store user article
{
"articles": {
"$article_id": {
"name": "Some name",
"$uid": "32431kjkjfj232"
},
"$article_id": {
"name": "Some name",
"$uid": "5555fffflllll"
}
}
}
So as I understood right when I will request articles it will contain all articles? Let me know if I am on right way with understanding of this uid and data binding.
Well, I'd say you're almost right. Just a few notes:
Inside article's JSON structure, since the '$' dollar mark on '$uid' field, usually, refers to a variable on firebase's database, I'd recommend you to change it to something else like 'userUID' or anything you want. Leaving the structure to something like this:
{
"articles": {
"$article_id": {
"name": "Some name",
"userUID": "32431kjkjfj232"
},
"$article_id": {
"name": "Some name",
"userUID": "5555fffflllll"
}
}
}
Then, you just query them as follow:
Using Swift 4 and Firebase 4:
let articlesRef = Database.database().reference(withPath: "articles")
articlesRef.observe(.value) { snapshot in
// Snopshot will contain all articles on your db reference.
print(snapshot)
}
let userUID = "..."
let articlesByUser = articlesRef.queryOrdered(byChild: "userUID").queryEqual(toValue: userUID)
articlesByUser.observe(.value) { snapshot in
// In this case, snapshot will contain all articles that has the 'userUID' key equal to what you've passed (what in this case, is the value of userUID constant.)
print(snapshot)
}

Listing Firebase data and relationships

I have just started working with Firebase database and I am a bit confused how to structure the database. In the following example I have a users object and a groups object. Each user can be part of multiple groups and every group can have multiple users. The proposed database structure is the following, according to "Structure Your Database".
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
"groups": {
"techpioneers": true,
"womentechmakers": true
}
}
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"startDate": "24-04-1820",
"members": {
"alovelace": true,
"ghopper": true,
"eclarke": true
}
}
}
}
Let's say I want to display all groups in a list in my app, with the group name and start date. How would I make that database call? Since the user object only contains the id of the groups, would I then have to make a separate call to the database for every group just to find out the name and start date of the group? If there are many groups in the list, then that becomes a lot of calls. My group might contain a lot of other information as well so this doesn't seem good for performance. Can I get all the groups in the groups list of the user, in one call?
One though I had was to include the name and start date in the groups object under the user:
"users": {
"alovelace": {
"name": "Ada Lovelace",
"groups": {
"techpioneers":{
"name": "Historical Tech Pioneers",
"startDate": "24-04-1820"
},
"womentechmakers":{
"name": "Women in Technology",
"startDate": "13-10-1823"
}
}
}
}
}
but this solution seems to add a lot of duplicate data. Also if I want to update the name I would have to do that in multiple locations. And maybe I want to add a sponsor organization object, that also contains group, and then want to list them. Then there would be 3 places to update the information on. How would I solve this?
You would then have two possibilities, one would be to store the data you need (duplicating it) in the groups node of each user.
The other, which is the one that I would recommend the most, would be to add an observeSingleEvent(of: .value) inside your first observer (that could be an observe(.value), observe(.childAdded) or whatever).
Say you have an array of all your group members, and an object named AppUser that represents a user in your app :
var groupMembers = [AppUser]()
To detect whenever a new member is added to a group for example, you could use a .childAdded observer for example :
func groupWasAddedObserver(completion: #escaping () -> ()) {
// Add a .childAdded observer to the group's members node (groupId should be defined somewhere) :
groupsRef.child(groupId).child("members").observe(.childAdded, with: { [weak self] (snapshot) in
// When you get the snapshot, store its key, which represents the member id :
let memberId = snapshot.key
// fetch this member's profile information using its id :
self?.getUser(memberId, completion: { (groupMember) in
// Once you got the profile information of this member, add it to an array of all your members for example :
self?.groupMembers.append(groupMember)
// Call the completion handler so that you can update the UI or reload a table view somewhere maybe depending on your needs :
completion()
})
})
}
And the second method to fetch a user data knowing his or her id :
func getUser(_ userId: String, completion: #escaping (AppUser) -> ()) {
// Add the observerSingleEvent observer :
usersRef.child(userId).observeSingleEvent(of: .value, with: { (snapshot) in
// Get the data you need using the snapshot you get :
guard let email = snapshot.childSnapshot(forPath: "email").value as? String else { return }
guard let name = snapshot.childSnapshot(forPath: "name").value as? String else { return }
guard let picUrl = snapshot.childSnapshot(forPath: "picUrl").value as? String else { return }
// Call the completion handler to return your user/member :
completion(AppUser(id: snapshot.key, email: email, name: name, picUrl: picUrl))
})
}
As you can see you get the memberId of each user using the snapshot key, and you use this memberId to fetch this specific user data.

Resources