How to nest data in Firebase Realtime database (Swift) - swift

My database in Firebase currently looks like this:
locations: {
person: ""
location: ""
}
How do I nest the data so that it looks something like this:
locations: {
person: "" {
location: ""
}
}
A structure where locations is the parent, person is the child, and location is the child of person.
I know the relationships, I just am not too sure of the syntax.
Here is the code I currently have, written in Swift:
let locRef = locationRef.childByAutoId()
let locItem = [
"location": getLocation(),
"person": senderId
]
locRef.setValue(locItem)
Thanks.

let locRef = locationRef.childByAutoId()
let locItem = [
senderId : [
"location": getLocationID()
]
]
SenderID will be the key for the person, so you can find the Person by its ID... And the ID is holding a nested location key
Do you only need the logic? Or do you need working code example?

Related

SwiftUI: Trying to set my CoreData model for decoded JSON

I have a small app that doesn't have persistence. I have JSON file of 100+ countries as part of the app:
Countries.json (only included 1 for brevity)
[
{
"id": 0,
"display_name": "Algeria",
"searchable_names": [
"algeria"
],
"image": "algeria",
"latitude": 36.753889,
"longitude": 3.058889,
"favorite": false,
"extended": false
}
]
and my country model looks like this:
Country.swift
import Foundation
struct Country: Codable, Identifiable {
var id: Int
let display_name: String
let searchable_names: [String]
let image: String
let latitude: Double
let longitude: Double
var favorite: Bool
var extended: Bool
}
and I'd decode the JSON like this:
#State var countries: [Country] = Bundle.main.decode("Countries.json")
Now, I am wanting to add a way to persist data so that if a user togglers favorite, that country will stay marked as a favorite on subsequent app visits.
I am having trouble in how to translate this over to use CoreData. I tried making a CoreData model called Country and added each property in but I feel like that is not correct. I basically need a way to add this decoded JSON to CoreData so that I could manipulate a single countries favorite value. I am bit lost at this point in how to accomplish this with a CoreData model.

Vapor 4: how to include siblings including extra properties from the pivot table?

I am struggling a lot with how to return a model that contains a many-to-many relationship via a pivot table that contains extra fields. Basically, I want to return the full pivot table with the extra fields, but I can't figure how to do this.
Let's consider the following 3 models: Course, User, and the pivot between them, a Student. The Student model contains the extra field progress.
final class Course: Model, Content {
static let schema = "courses"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
init() { }
}
final class Student: Model {
static let schema = "students"
#ID(key: .id)
var id: UUID?
#Parent(key: "course_id")
var course: Course
#Parent(key: "user_id")
var user: User
#Field(key: "progress")
var progress: Int
init() { }
}
final class User: Model, Content {
static let schema = "users"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
#Field(key: "private")
var somePrivateField: String
init() { }
}
I have a route like this, which returns an array of courses:
func list(req: Request) throws -> EventLoopFuture<[Course]> {
return Course
.query(on: req.db)
.all()
.get()
}
The resulting JSON looks something like this:
[
{
"id": 1,
"name": "Course 1"
}
]
How can I also include the array of students, so that the end result is something like this?
[
{
"id": 1,
"name": "Course 1",
"students": [
{
"user": {
"id": 1,
"name": "User 1"
},
"progress": 0
},
{
"user": {
"id": 2,
"name": "User 2"
},
"progress": 100
},
]
]
I can add the users to the Course model like this:
#Siblings(through: Student.self, from: \.$course, to: \.$user)
public var users: [User]
And then change my route like this:
func list(req: Request) throws -> EventLoopFuture<[Course]> {
return Course
.query(on: req.db)
.with(\.$user)
.all()
.get()
}
But that only adds the user info to the result, NOT the extra properties on the pivot table (namely, progress). It kinda seems to me that even though pivot tables can have extra properties and the docs even specifically point that out, there are no good ways of actually dealing with this scenario since #Siblings don't point to the pivot at all.
Bonus question: I'd want the User model be mapped to a PublicUser model, so that private/internal fields are not part of the JSON result. See this question for what I mean. I want to do that same thing, but with the Student pivot's User model. Complicated, I know 😬
I encountered the same issue about accessing additional fields in the pivot table, but there is a fairly tidy way of accomplishing this. In addition to your siblings relationship, define a #Children relation from Course to Student. Then, in your query, do a nested with.
Put this in your Course model:
#Children(for:\.$course) var students: [Student]
Query:
let query = Course.query(on: req.db).with(\.$students){ $0.with(\.$user) }.all()
The first with gets the additional fields of the pivot table and then the nested with get the User model.
You can query and use the extra properties on the pivot table directly using the pivots property on the Sibling relation which eagerly loads the pivot table objects through the sibling relation for easy access.
Eg:
Course.query(on: db)
.with(\.$user)
.with(\.$user.$pivots).all().map { course in
// you can now access the loaded students using:
let students = course.$user.pivots
...
}

Algolia sorting by geolocation in Swift

I have a problem with Algolia geolocation sorting in iOS App.
I need to display all documents in a range of 100 km from the user location.
I have multiple documents in my Indice. The document looks like this:
"document": {
"price": 5000,
"unit": "cały projekt",
"_geoloc": {
"lat": 54.5,
"lng": 18.55
},
"title": "Test ogloszenia",
"range": 0,
"activeFrom": {
"_seconds": 1597042800,
"_nanoseconds": 0
}
}
In my Algolia Ranking and Sorting, I have default set GEO.
My Swift code for sorting locations looks like below:
func getAnnouncesLocation(location: CLLocationCoordinate2D, completion: #escaping ([Announcement]) -> ()) {
var announcementsArray = [Announcement]()
let query = Query(query: "")
query.aroundLatLng = LatLng(lat: location.latitude, lng: location.longitude)
query.aroundRadius = .explicit(100000) // 100 km
collectionIndex = searchClient.index(withName: "products_geolocation")
collectionIndex.search(query) { (content, error) in
guard let content = content else {
if let error = error {
print(error.localizedDescription)
}
return
}
print("HITS \(content)")
}
}
The code doesn't return any error but the content is empty.
Another sorting like by price works perfectly. The only problem is with geolocation.
If that can help to set sorting by the price I need to add a sort-by attribute like this: document.price in Dashboard.
I am saving data to Algolia from my server in Node.js and there I am creating _geoloc value.
The latitude and longitude are hardcoded for testing so there isn’t a problem with async.
Thank you for any kind of help.
Regards
Matt
I found a bug in my project.
While sending the document to Algolia I send like this:
const record = {
objectID: doc.id,
document: document
};
where document contained all properties and also _geoloc property. After some investigation, I separated _geoloc from the document I sent it like below:
const record = {
objectID: doc.id,
document: document,
_geoloc: coordinates
};
Now everything works fine.
Cheers

Realm query problem with sorted localized data

Consider the following Realm models:
class Fruit: Object {
#objc dynamic var name = ""
let localizations = List<Localization>()
/**
Returns the localized name of the fruit matching the preferred language of the app
or self.name if the fruit does not have a localization matching the user preferred language codes.
*/
var localizedName: String? {
guard !Locale.isPreferredLanguageDefaultAppLanguage else { return self.name }
let preferredLanguagesCodes = Locale.preferredLanguagesCodes
let localizations = preferredLanguagesCodes.compactMap({ languageCode in Array(self.localizations).filter({ $0.languageCode == languageCode }).first })
return localizations.first?.localizedName ?? self.name
}
}
class Localization: Object {
#objc dynamic var localizedName: String = ""
#objc dynamic var languageCode: String = ""
}
Let's say I have 2 fruits in my database (represented in JSON format for the sake of simplicity):
[
{
"name": "Apple",
"localizations": [
{
"localizedName": "Pomme",
"languageCode": "fr"
}
]
},
{
"name": "Banana",
"localizations": [
{
"localizedName": "Banane",
"languageCode": "fr"
}
]
}
]
Now I want to get all the fruits in my database, and sort them alphabetically by their localizedName.
var localizedNameSortingBlock: ((Fruit, Fruit) -> Bool) = {
guard let localizedNameA = $0.localizedName, let localizedNameB = $1.localizedName else { return false }
return localizedNameA.diacriticInsensitive < localizedNameB.diacriticInsensitive
}
let sortedFruits = Array(Realm().objects(Fruit.self)).sorted(by: localizedNameSortingBlock)
If the first preferred language of my device is "English", I get this:
Apple
Banana
If it's set to "French":
Banane
Pomme
It's quite simple, but this solution has a major inconvenient:
By casting the Results<Fruit> collection into an array, I'm loosing the ability to get live updates via Realm's notification token system.
The problem is, I can't sort using NSPredicate directly on the Results collection, because the localizedName property is a computed property, thus is ignored by Realm.
I thought about writing the localizedName value directly into the name property of the Fruit object, but doing so requires to loop through all fruits and change their name whenever the user change of preferred language. There must be a better way.
So my question is:
Is there a way to retrieve all the fruits in my database, get them sorted by their localizedName, without loosing the ability to receive batch updates from Realm?

swift3 - how do I access dictionary in an array?

I want to make an array of several dictionaries for my world clock app like this:
let countriesList = [
["country": "Tokyo, Japan", "gmc": "GMT+9"],
["country": "New York, USA", "gmc": "GMT-5"]
]
Since I'm getting no error, I'm assuming this is okay.
Now my question is, how do I access each country?
I'm trying to list them all into a table, and I have googled but I got nothing. Can anyone help?
it's highly recommended to use a custom struct for that purpose:
struct Country {
let name, gmc : String
}
let countriesList = [
Country(name: "Tokyo, Japan", gmc: "GMT+9"),
Country(name: "New York", gmc: "GMT-5")
]
Now in cellForRow you can easily access the properties:
let country = countriesList[indexPath.row]
cell.textLabel?.text = country.name
cell.detailTextLabel?.text = country.gmc
No key subscription, no type casting, no optional binding, no problems ;-)
You can print all the names with
print(countryList.map{ $0.name })
In your cellForRowAt method where you are setting country to label.
cell.lblCountry.text = self.countriesList[indexPath.row]["country"] as? String
If you want all country list from your array of dictionary.
let countries = self.countriesList.flatMap { $0["country"] as? String }
With this you will get array of string with all country value.
You can use subscript:
for country in countriesList {
print(country["country"])
}