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.
Related
So I've been working with a nested JSON file (that I added locally to my project) in Swift. I've included a part of the JSON file I'm working on below. The data is structured as follows:
{
"categories": [
{
"categoryName": "Albatrosses",
"exercisesInCategory": [
"Wandering albatross",
"Grey-headed albatross",
"Black-browed albatross",
"Sooty albatross",
"Light-mantled albatross"
]
},
{
"categoryName": "Cormorants",
"exercisesInCategory": [
"Antarctic shag",
"Imperial shag",
"Crozet shag"
]
},
{
"categoryName": "Diving petrels",
"exercisesInCategory": [
"South Georgia diving petrel",
"Common diving petrel"
]
},
{
"categoryName": "Ducks, geese and swans",
"exercisesInCategory": [
"Yellow-billed pintail"
]
}
]
}
In order to retrieve the data I made 2 structures that represent the data in the JSON so I can then retrieve values from it. These are as follows:
struct Response:Codable{
let categories: [Categories]
}
struct Categories:Codable{
let categoryName : String?
let exercisesInCategory : [String]
}
The file name is fitnessData.json and I'm trying to retrieve the data from it by using this code:
private func parse(){
print("Retrieving JSON Data...")
if let url = Bundle.main.url(forResource: "fitnessData", withExtension: "json") {
do {
let data = try Data(contentsOf: url)
self.response = try JSONDecoder().decode(Response.self, from: data)
if let responseJSON = self.response {
print("The categories are: ", responseJSON.categories[1].categoryName!)
}
} catch {
print(error)
}
}
}
The problem is that I would like to retrieve ALL the 'categoryName' values from the JSON file, and ALL the 'exercisesInCategory' values. But so far I've only managed to navigate towards a specific item in the JSON file and retrieving that item i.e.
responseJSON.categories[1].categoryName!
I would like to iterate over the JSON file to get all of the 'categoryName' values for example. However in order to do that I'd have to write something like this:
for value in responseJSON.categories[1].categoryName! {
print(value)
}
Where '1' represents all the values for the categories struct. The code above will obviously print only the categoryName of the second index in the categories array in the JSON file. Could someone point me in the right direction?
You can do that like:
for category in responseJSON.categories {
print(category.categoryName!)
}
Or you can use map function for getting all the categoryName like:
let categoryNames = responseJSON.categories.map {$0.categoryName}
Simply like this.
response.categories.forEach { print($0.categoryName) }
If you would like to put both values in different arrays:
var categoryNameList = [String]
var excercisesInCategory = [[String]]
for category in responseJSON.categories {
categoryNameList.append(category.categoryName)
excercisesInCategory.append(category. exercisesInCategory)
}
this.
let categories = responseJSON.categories.map { $0.categoryName }
categories.forEach { print($0) }
If you iterate through the String, each item is single character of string.
You can iterate through categories array
for category in responseJSON.categories {
print(category.categoryName ?? "No name")
}
To get all names, you can use compactMap which removes nil values from an array
let names = responseJSON.categories.compactMap { $0.categoryName }
Next:
If each category has its name, make type of this property non-optional String (then you can use map instead of compactMap)
I would improve your naming. Rename categoryName key of json to name using CodingKeys enum. Then you could do
category.name
If you wanted to get all the categoryName and exercisesInCategory form JSON file, then you don't need to pass hard coded index. Use the following modified function, it will do the work..
private func parse(){
print("Retrieving JSON Data...")
if let url = Bundle.main.url(forResource: "fitnessData", withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let response = try JSONDecoder().decode(Response.self, from: data)
for category in response.categories {
print("Category Name: \(category.categoryName!)")
for exercises in category.exercisesInCategory {
print("Exercise in category: \(exercises)")
}
}
} catch {
print(error)
}
}
}
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.
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.
https://github.com/daltoniam/JSONJoy-Swift
For example :
JSON1 = {
"message": "Sorry! Password does not match.",
"code": "4"
}
JOSN2 = {
"data": {
"id": 21
},
"message": "Signup Successful.",
"code": "1"
},
Here json key “data” is optional. Then how I can handle both response using the same model object??
JSONJoy natively sets not found elements to nil, you just have to declare them optional and then check for nil before using them.
From the docs
This also has automatic optional validation like most Swift JSON libraries.
//some randomly incorrect key. This will work fine and the property
will just be nil.
firstName = decoder[5]["wrongKey"]["MoreWrong"].string
//firstName is nil, but no crashing!
Here is my example that my be illustrative. I have a complex object set where my top level object (UserPrefs) has arrays of secondary objects (SmartNetworkNotification and SmartNotificationTime).
Note that notifications and times are both declared as optional. What I do is check for nil after attempting to parse the secondary object arrays. Without the nil check the attempt to iterate on the parsed list fails since its nil. With the nil check it just moves past it if its empty.
This works for me but isn't deeply tested yet. YMMV! Curious how others are handling it.
struct UserPrefs: JSONJoy {
var notifications: [SmartNetworkNotification]?
var times: [SmartNotificationTime]?
init(_ decoder: JSONDecoder) throws {
// Extract notifications
let notificationsJson = try decoder["notifications"].array
if(notificationsJson != nil){
var collectNotifications = [SmartNetworkNotification]()
for notificationDecoder in notificationsJson! {
do {
try collectNotifications.append(SmartNetworkNotification(notificationDecoder))
} catch let error {
print("Error.. on notifications decoder")
print(error)
}
}
notifications = collectNotifications
}
// Extract time of day settings
let timesJson = try decoder["times"].array
if(timesJson != nil){
var collectTimes = [SmartNotificationTime]()
for timesDecoder in timesJson! {
do {
try collectTimes.append(SmartNotificationTime(timesDecoder))
} catch let error {
print("Error.. on timesJson decoder")
print(error)
}
}
times = collectTimes
}
}
I am using firebase as backend for my app. My app will be some sort of expense tracker. The way that I am thinking I should restructure my data so that I can filter the dates easily is as such.
uid/expenses/category/year/month/day/uniquekey/details
to dig into the snapshots would be something like this
func retrieveData (){
_ = dataRef.observeEventType(.ChildAdded, withBlock: { (snapshotOne) in
if let snapshotTwo = snapshotOne.children.allObjects as? [FIRDataSnapshot] {
if let snapshotThree = snapshotTwo.children.allObjects as? [FIRDataSnapshot] {
if let snapshotFour = snapshotThree.children.allObjects as? [FIRDataSnapshot] {
for item in snapshotFour {
//retrieve details
}
}
}
}
}
}
I have no idea what the consequences are to have to dig so deep into the database and whether my structure for database is okay? could anyone advise?
Any Change to Data structure will effect your database.
Firebase prefers Flattened Data
& Using Indices to Define Complex Relationships:
A lot of times in building apps, it's preferable to download a subset of a list. This is particularly common if the list contains thousands of records or more. When this relationship is static, and one-directional, we can use queries to grab a subset of data, or simply nest the entries under the logical grouping.
{
"messages": {
"john": {
"rec1": "Walk the dog",
"rec2": "Buy milk",
"rec3": "Win a gold medal in the Olympics"
}
}
}
However, we already know that flattening data is a best practice. So let's see why, by examining where this structure begins to break down. If we move into something more dynamic, like shared chat rooms, then suddenly our data (e.g. lists of rooms, lists of messages) now have two-way relationships.
Users can belong to a group and groups comprise a list of users. A first attempt at resolving this data structure would probably look this:
{
"users": {
"mchen": { "name": "Mary Chen" },
"brinchen": { "name": "Byambyn Rinchen" },
"hmadi": { "name": "Hamadi Madi" }
},
"groups": {
"alpha": {
"name": "Alpha Tango",
"members": {
"m1": "mchen",
"m2": "brinchen",
"m3": "hamadi"
}
},
"bravo": { ... },
"charlie": { ... }
}
}
You can read more here.
https://www.firebase.com/docs/web/guide/structuring-data.html
The whole structure of your database is related to the data you structure to store.
Also consider reading more about the Security and Rules.
https://www.firebase.com/docs/security/quickstart.html