How to display nested objects in firebase data - swift

How to display the below data in a view:
[
"parties" {
"2016-12-30" {
"uid" {
"name": "joe",
"age": "32"
},
"uid" {
"name": "kevin",
"age": "29"
}
},
"2016-12-25" {
"uid" {
"name": "ben"
"age": "44"
}
}
}
]
In my controller I have
var parties = [Party]()
DataService.ds.REF_PARTIES.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
print(snapshot)
for snap in snapshot {
if let partyDict = snap.value as? [String: AnyObject] {
let key = snap.key // the party date
let party = Party(date: key, partyData: partyDict)
self.parties.append(party)
}
}
}
self.tableView.reloadData()
})
class PartyFeedCell: UITableViewCell:
func configureCell(party: Party) {
dateLabel.text = part.date // date as expected
}
In react, I could use Object.key to go deeper in the loop and get the users for those dates (keys). Any way to accomplish this? Under, say xxx date, I should see a list of users.
Party.swift:
import Foundation
class Party {
private var _name: String!
private var _age: String!
var name: String {
return _name
}
[...]
init(name: String, age: String) {
self._name = name
self._age = age
}
init(date: String, partyData: [String: AnyObject]) {
self._date = date
[...]
}
}
DataService.swift:
import Foundation
import FirebaseDatabase
let DB_BASE = FIRDatabase.database().reference()
class DataService {
static let ds = DataService()
private var _REF_BASE = DB_BASE
private var _REF_PARTIES = DB_BASE.child("parties").queryOrderedByKey()
var REF_BASE: FIRDatabaseReference {
return _REF_BASE
}
var REF_PARTIES: FIRDatabaseQuery {
return _REF_PARTIES
}
}

Two things:
Using dates as key's... while it may be ok, should probably be changed - best practice is to disassociate keys from the data contained in the children.
parties
-Y9809s0asas
date: "2016-12-30"
users:
uid_0: true
uid_1: true
-Y89js99skks
date: "2016-12-15"
users:
uid_1: true
uid_3: true
users
uid_0:
name: "ben"
age: "42"
uid_1:
name: "frank"
age: "28"
etc
The keys within parties are generated by childByAutoId (Y9809s0asas etc)
Second thing is that the code looks pretty much ok. The question appears to be how to get a value from a Dictionary?
In this case the code assigns the snap.value as a dictionary of [String: AnyObject] to partyDict. So at that point partyDict is just a Dictionary of key:value pairs
So in your Party class just a simple assignment is all that needs to be done:
init(date: String, partyData: [String: AnyObject]) {
self._date = date
self._age = partyData["age"]
self._name = partyData["name"]
}
I didn't run your code but it's pretty close to being usable.
Oh - with this change in structure you will want to add one more property to the Party class, key. Make sure you pass the parent node name (snap.key) into the class so if it needs to be updated etc it will have the parent node name stored (the Y9809s0asas from the above structure)

Related

How to declare a multidimensional String in Realm?

This bounty has ended. Answers to this question are eligible for a +50 reputation bounty. Bounty grace period ends in 13 hours.
Dipesh Pokhrel wants to draw more attention to this question:
I have gone throught the documentation no where its specifically mentioned. how to deal with the multidimensional string objects
I have a realm class which contains the a multi dimensional string, realm in Decodable is throwing an error to while parsing, created class to support realm.
class Categories : Object,Decodable {
// var assetSum : [[String]]? // In swift originally
let assetSum = RealmSwift.List<String>() // modified to support list
#objc var id : String?
#objc var dn : String?
How to fix this , to be more Generalise how to store var assetSum : [[String]]? this kind of value in realm?
I have gone through the documentation of realm but could not find something related to this
Realm supports basic types like Int, String, Date etc. and several collections types like List (Array), Map (Dictionary) from the box. For the other your custom types you can use json serialization which works pretty quick.
It can be implemented with two variables where persistent private one is for storing data and public one is for accessing e.g:
import RealmSwift
class Categories: Object {
#Persisted private var assetSum: Data?
var assetSumValue: [[String]]? {
get {
guard let value = assetSum else {
return nil
}
return try? JSONDecoder().decode(([[String]]?).self, from: value)
}
set {
assetSum = try? JSONEncoder().encode(newValue)
}
}
}
Now you can easy set/get values with assetSumValue:
// Create and save
let categories = Categories()
try realm.write {
categories.assetSumValue = [["1", "2", "3"], ["4", "5", "6"]]
realm.add(categories)
}
// Get first element from DB
if let categories = realm.objects(Categories.self).first,
let value = categories.assetSumValue
{
print(value) // Prints: [["1", "2", "3"], ["4", "5", "6"]]
}
In case of encoding/decoding your custom Realm types with complex properties you should implement a custom decoder:
class Categories: Object, Codable {
#Persisted var id: String?
#Persisted var dn: String?
#Persisted private var assetSum: Data?
var assetSumValue: [[String]]? {
get {
guard let value = assetSum else {
return nil
}
return try? JSONDecoder().decode(([[String]]?).self, from: value)
}
set {
assetSum = try? JSONEncoder().encode(newValue)
}
}
override init() {
super.init()
}
required init(from decoder: Decoder) throws {
super.init()
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode((String?).self, forKey: .id)
dn = try values.decode((String?).self, forKey: .dn)
assetSumValue = try values.decode(([[String]]?).self, forKey: .assetSum)
}
}
How to decode:
let json = """
{
"id": "100",
"dn": "200",
"assetSum": [
["one", "two", "three"],
["four", "five", "six"]
]
}
"""
let categories = try JSONDecoder().decode(Categories.self, from: json.data(using: .utf8)!)
if let value = categories.assetSumValue {
print(value) // Prints [["one", "two", "three"], ["four", "five", "six"]]
}

Need acces from document to collection Firestore

I'm trying to do an iOS app and i've binded it with firebase, so I'm trying to get some posts ad fetch them, and this works fine, however this posts got 2 collections (likes and replies) and i'm trying to fetch likes, the thing is that I can't get the likes because for some reasons I can't a class for document forEach neither I can't access it, someone got any idea?
Code:
import Foundation
import Firebase
struct Post : Hashable {
var id : String
var dateAdded : String
var posterEmail : String
var posterUsername : String
var posterIcon : String
var postTitle : String
var postBody : String
var likes : [String]
var userLikedPost : Bool
}
struct Like {
var likeId : String
var likerEmail : String
}
class Likes {
var likes : [Like] = []
func fetchLikes() {
//Firestore.firestore()
}
}
class Posts : ObservableObject {
#Published var posts : [Post] = []
func fetchPosts() {
Firestore.firestore().collection("posts").getDocuments(completion: { (docPosts, error) in
if (error != nil) {
print("error fetching posts")
} else {
docPosts?.documents.forEach { (post) in
let id = post.documentID
let email = post.get("posterEmail") as! String
let username = post.get("posterUsername") as! String
let icon = post.get("posterIcon") as! String
let title = post.get("title") as! String
let body = post.get("body") as! String
// Here i want to insert the code that gets the likes class and access the likes variable
self.posts.append(Post(id: id, dateAdded:String(id.split(separator: "_").joined(separator: "/").prefix(10)) ,posterEmail: email, posterUsername: username, posterIcon: icon, postTitle: title, postBody: body,
likes: [],userLikedPost: false))
}
}
})
}
}
The Firestore structure was not included in the question so I will present one for use
user_wines
uid_0
name: "Jay"
favorite_wines:
0: "Insignia"
1: "Scavino Bricco Barolo"
2: "Lynch Bages"
uid_1
name: "Cindy"
favorite_wines
0: "Palermo"
1: "Mercury Head"
2: "Scarecrow"
And then the code to read all of the user documents, get the name, the wine list (as an array as Strings) and output it to console
func readArrayOfStrings() {
let usersCollection = self.db.collection("user_wines")
usersCollection.getDocuments(completion: { snapshot, error in
guard let allDocs = snapshot?.documents else { return }
for doc in allDocs {
let name = doc.get("name") as? String ?? "No Name"
let wines = doc.get("favorite_wines") as? [String] ?? []
wines.forEach { print(" ", $0) }
}
})
}
and the output
Jay
Insignia
Scavino Bricco Barolo
Lynch Bages
Cindy
Palermo
Mercury Head
Scarecrow
EDIT
Here's the same code using Codable
class UserWineClass: Codable {
#DocumentID var id: String?
var name: String
var favorite_wines: [String]
}
and the code to read data into the class
for doc in allDocs {
do {
let userWine = try doc.data(as: UserWineClass.self)
print(userWine.name)
userWine.favorite_wines.forEach { print(" ", $0) }
} catch {
print(error)
}
}

Firebase Realtime Database in Swift

After several hours of trying to figure out what's happening without finding an answer to fill my void anywhere, I finally decided to ask here.
While I assume I do have a concurrency issue, I have no clue as to how to solve it...
I have an application trying to pull data from a Firebase Realtime Database with the following content:
{
"products" : {
"accessory" : {
"foo1" : {
"ean" : 8793462789134,
"name" : "Foo 1"
},
"foo2" : {
"ean" : 8793462789135,
"name" : "Foo 2"
}
},
"cpu" : {
"foo3" : {
"ean" : 8793462789115,
"name" : "Foo 3"
}
},
"ios" : {
"foo4" : {
"ean" : 8793462789120,
"name" : "Foo 4"
},
"foo5" : {
"ean" : 8793462789123,
"name" : "Foo 5"
}
}
}
}
I have a data model in Product.swift:
class Product {
var identifier: String
var category: String
var ean: Int
var name: String
init(identifier: String, category: String, ean: Int, name: String) {
self.init(identifier: identifier)
self.category = category
self.ean = ean
self.name = name
}
}
I want to fetch the data in another class called FirebaseFactory.swift. I plan to use to communicate with Firebase:
import Foundation
import FirebaseDatabase
class FirebaseFactory {
var ref = Database.database().reference()
func getAvailableProducts() -> [Product] {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// 1st print statement
print("From within closure: \(data)")
}
// 2nd print statement
print("From outside the closure: \(data)")
// Getting the products yet to be implemented...
return products
}
}
For now, I am simply trying to call the getAvailableProducts() -> [Product] function from one of my view controllers:
override func viewWillAppear(_ animated: Bool) {
let products = FirebaseFactory().getAvailableProducts()
}
My Problem now is that the 2nd print is printed prior to the 1st – which also means that retrieving the data from the snapshot and assigning it to data variable does not take place. (I know that the code to create my Product objects is missing, but that part actually is not my issue – concurrency is...)
Any hints – before I pull out any more of my hairs – is highly appreciated!!
You're on the right track with your theory: the behavior you're describing is how asynchronous data works with closures. You've experienced how this causes problems with returning the data you want. It's a very common question. In fact, I wrote a blog on this recently, and I recommend you check it out so you can apply the solution: incorporating closures into your functions. Here's what that looks like in the particular case you've shown:
func getAvailableProducts(completion: #escaping ([Product])-> Void) {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// do whatever you were planning on doing to return your data into products... probably something like
/*
for snap in snapshot.children.allObjects as? [DataSnapshot] {
let product = makeProduct(snap)
products.append(product)
}
*/
completion(products)
}
}
Then in viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
FirebaseFactory().getAvailableProducts(){ productsArray in
// do something with your products
self.products = productsArray
// maybe reload data if you have a tableview
self.tableView.reloadData()
}
}
If I understand your question, you need to return the data after the event occurs, because is an async event
class FirebaseFactory {
var ref = Database.database().reference()
func getAvailableProducts() -> [Product] {
var products = [Product]()
var data: DataSnapshot = DataSnapshot()
self.ref.child("products").queryOrderedByKey().observeSingleEvent(of: .value) { (snapshot) in
data = snapshot
// 1st print statement
print("From within closure: \(data)")
// Process here the snapshot and insert the products in the array
return products
}
}
}

Can I use Swift's map() on Protocols?

I have some model code where I have some Thoughts that i want to read and write to plists. I have the following code:
protocol Note {
var body: String { get }
var author: String { get }
var favorite: Bool { get set }
var creationDate: Date { get }
var id: UUID { get }
var plistRepresentation: [String: Any] { get }
init(plist: [String: Any])
}
struct Thought: Note {
let body: String
let author: String
var favorite: Bool
let creationDate: Date
let id: UUID
}
extension Thought {
var plistRepresentation: [String: Any] {
return [
"body": body as Any,
"author": author as Any,
"favorite": favorite as Any,
"creationDate": creationDate as Any,
"id": id.uuidString as Any
]
}
init(plist: [String: Any]) {
body = plist["body"] as! String
author = plist["author"] as! String
favorite = plist["favorite"] as! Bool
creationDate = plist["creationDate"] as! Date
id = UUID(uuidString: plist["id"] as! String)!
}
}
for my data model, then down in my data write controller I have this method:
func fetchNotes() -> [Note] {
guard let notePlists = NSArray(contentsOf: notesFileURL) as? [[String: Any]] else {
return []
}
return notePlists.map(Note.init(plist:))
}
For some reason the line return notePlists.map(Note.init(plist:)) gives the error 'map' produces '[T]', not the expected contextual result type '[Note]'
However, If I replace the line with return notePlists.map(Thought.init(plist:)) I have no issues. Clearly I can't map the initializer of a protocol? Why not and what's an alternate solution?
If you expect to have multiple types conforming to Note and would like to know which type of note it is stored in your dictionary you need to add an enumeration to your protocol with all your note types.
enum NoteType {
case thought
}
add it to your protocol.
protocol Note {
var noteType: NoteType { get }
// ...
}
and add it to your Note objects:
struct Thought: Note {
let noteType: NoteType = .thought
// ...
}
This way you can read this property from your dictionary and map it accordingly.

Multiple Realm objects to JSON

I am trying to convert Realm Object into JSON. My version is working but not if you want to put multiple objects into JSON. So my question is, how should you add multiple Realm Objects into JSON?
Something like that:
{
"Users": [
{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}
],
"Posts": [
{"id": "1","title": "hey"},{"id": "2","title": "hey2"},{"id": "3","title": "hey3"}
]
}
This is what I am doing right now:
func getRealmJSON(name: String, realmObject: Object, realmType: Any) -> String {
do {
let realm = try Realm()
let table = realm.objects(realmType as! Object.Type)
if table.count == 0 {return "Empty Table"}
let mirrored_object = Mirror(reflecting: realmObject)
var properties = [String]()
for (_, attr) in mirrored_object.children.enumerated() {
if let property_name = attr.label as String! {
properties.append(property_name)
}
}
var jsonObject = "{\"\(name)\": ["
for i in 1...table.count {
var str = "{"
var insideStr = String()
for property in properties {
let filteredTable = table.value(forKey: property) as! [Any]
insideStr += "\"\(property)\": \"\(filteredTable[i - 1])\","
}
let index = insideStr.characters.index(insideStr.startIndex, offsetBy: (insideStr.count - 2))
insideStr = String(insideStr[...index])
str += "\(insideStr)},"
jsonObject.append(str)
}
let index = jsonObject.characters.index(jsonObject.startIndex, offsetBy: (jsonObject.count - 2))
jsonObject = "\(String(jsonObject[...index]))]}"
return jsonObject
}catch let error { print("\(error)") }
return "Problem reading Realm"
}
Above function does like that, which is good for only one object:
{"Users": [{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}]}
Like this I call it out:
let users = getRealmJSON(name: "Users", realmObject: Users(), realmType: Users.self)
let posts = getRealmJSON(name: "Posts", realmObject: Posts(), realmType: Posts.self)
And I tried to attach them.
Can anybody please lead me to the right track?
You can use data models to encode/decode your db data:
For example you have
class UserEntity: Object {
#objc dynamic var id: String = ""
#objc dynamic var createdAt: Date = Date()
#objc private dynamic var addressEntities = List<AddressEntity>()
var addresses: [Address] {
get {
return addressEntities.map { Address(entity: $0) }
}
set {
addressEntities.removeAll()
let newEntities = newValue.map { AddressEntity(address: $0) }
addressEntities.append(objectsIn: newEntities)
}
}
}
Here you hide addressEntities with private and declare addresses var with Address struct type to map entities into proper values;
And then use
struct User: Codable {
let id: String
let createdAt: Date
let addresses: [Address]
}
And then encode User struct any way you want