Need acces from document to collection Firestore - swift

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)
}
}

Related

Swift+Firestore - return from getDocument() function

I'm currently dealing with this problem. I have Firestore database. My goal is to fill friends array with User entities after being fetched. I call fetchFriends, which fetches currently logged user, that has friends array in it (each item is ID of friend). friends array is then looped and each ID of friend is fetched and new entity User is made. I want to map this friends array to friends Published variable. What I did there does not work and I'm not able to come up with some solution.
Firestore DB
User
- name: String
- friends: [String]
User model
struct User: Identifiable, Codable {
#DocumentID var id: String?
var name: String?
var email: String
var photoURL: URL?
var friends: [String]?
}
User ViewModel
#Published var friends = [User?]()
func fetchFriends(uid: String) {
let userRef = db.collection("users").document(uid)
userRef.addSnapshotListener { documentSnapshot, error in
do {
guard let user = try documentSnapshot?.data(as: User.self) else {
return
}
self.friends = user.friends!.compactMap({ friendUid in
self.fetchUserAndReturn(uid: friendUid) { friend in
return friend
}
})
}
catch {
print(error)
}
}
}
func fetchUserAndReturn(uid: String, callback:#escaping (User)->User) {
let friendRef = db.collection("users").document(uid)
friendRef.getDocument { document, error in
callback(try! document?.data(as: User.self) as! User)
}
}
One option is to use DispatchGroups to group up the reading of the users but really, the code in the question is not that far off.
There really is no need for compactMap as user id's are unique and so are documentId's within the same collection so there shouldn't be an issue where there are duplicate userUid's as friends.
Using the user object in the question, here's how to populate the friends array
func fetchFriends(uid: String) {
let userRef = db.collection("users").document(uid)
userRef.addSnapshotListener { documentSnapshot, error in
guard let user = try! documentSnapshot?.data(as: User.self) else { return }
user.friends!.forEach( { friendUid in
self.fetchUserAndReturn(uid: friendUid, completion: { returnedFriend in
self.friendsArray.append(returnedFriend)
})
})
}
}
func fetchUserAndReturn(uid: String, completion: #escaping ( MyUser ) -> Void ) {
let userDocument = self.db.collection("users").document(uid)
userDocument.getDocument(completion: { document, error in
guard let user = try! document?.data(as: User.self) else { return }
completion(user)
})
}
Note that I removed all the error checking for brevity so be sure to include checking for Firebase errors as well as nil objects.

Nil data returned when copying "working" json data to new struc array

Weird. I swear this was working but then it just stopped working .. or ... Please ignore the i+i ,I will clean this up...
I don't have a clue why but myrecords?[i].title is returning nil. The json.releases[i].date_adde is working fine and full of data. I can "print" it and get a result. but when I go to copy it to the myrecords it is returning nil.
I download the data from JSON, that works fine. then I try to copy the data to a struc array I can get to in other parts of my app and now my myrecords data is empty. what the heck am I doing wrong?
import Foundation
var numberOfRecords : Int = 0
struct routine {
var dateadded : String
var title : String
var artist : String
var year : Int
var recordlabel : String
}
var myrecords: [routine]?
//-------------------------------------------------------------
struct Response: Codable {
var pagination: MyResult
var releases: [MyReleases]
}
public struct MyResult: Codable {
var page: Int
var per_page: Int
var items: Int
}
public struct MyReleases: Codable {
var date_added: String
var basic_information: BasicInformation
}
public struct BasicInformation: Codable {
var title: String
var year: Int
var artists : [Artist]
var labels: [Label]
}
public struct Artist: Codable {
var name: String
}
public struct Label: Codable {
var name: String
}
let url = "https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?callback=&sort=added&sort_order=desc&per_page=1000"
public func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { data, response, error in
guard let data = data, error == nil else {
print("something went wrong")
return
}
//HAVE DATA
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("Converion Error:\(error.localizedDescription)")
}
guard let json = result else {
return
}
numberOfRecords = json.pagination.items
var i: Int
i = -1
for _ in json.releases {
i = i + 1
myrecords?[i].dateadded = json.releases[i].date_added
myrecords?[i].title = json.releases[i].basic_information.title
myrecords?[i].artist = json.releases[i].basic_information.artists[0].name
myrecords?[i].year = json.releases[i].basic_information.year
myrecords?[i].recordlabel = json.releases[i].basic_information.labels[0].name
print(">>>>>>\(myrecords?[i].dateadded)")
}
})
task.resume()
}
You haven't initialized myrecords array.
Otherwise, you cannot use subscript like myrecords[i] when you don't know the capacity of array, it can be out of index.
First, initialize your array.
var myrecords: [routine]? = []
Second, append new element to array instead of using subscript
for _ in json.releases {
let newRecord = routine()
newRecord.dateadded = json.releases[i].date_added
newRecord.title = json.releases[i].basic_information.title
newRecord.artist = json.releases[i].basic_information.artists[0].name
newRecord.year = json.releases[i].basic_information.year
newRecord.recordlabel = json.releases[i].basic_information.labels[0].name
myrecords.append(newRecord)
}
This is the answer. :) THANK YOU All for pointing me in the right direction
struct Routine {
var dateadded : String
var title : String
var artist : String
var year : Int
var recordlabel : String
}
var myRecords: [Routine] = []
var i : Int
i = -1
for _ in json.releases {
var newRecord = Routine.self(dateadded: "", title: "", artist: "", year: 0, recordlabel: "")
i = i + 1
newRecord.dateadded = json.releases[i].date_added
newRecord.title = json.releases[i].basic_information.title
newRecord.artist = json.releases[i].basic_information.artists[0].name
newRecord.year = json.releases[i].basic_information.year
newRecord.recordlabel = json.releases[i].basic_information.labels[0].name
myRecords.append(newRecord)
}
print(">>>>\(myRecords[0].dateadded)")
I will clean up the bad code too but it works and that is good! :)

Getting an array of objects using FirebaseFirestoreSwift

I have a struct that looks like this:
struct Joint: Identifiable, Codable {
#DocumentID var id : String? = UUID().uuidString
var serviceType : Int
var businessName : String
var keywords: [String]
var menu : [DishItems]
enum CodingKeys : String, CodingKey {
case businessName
case serviceType = "jointType"
case keywords
case menu
}
}
struct DishItems: Codable {
var listOrder : Int
var menuItemName : String
var menuItemDescription : String
var menuItemPrice : Double
}
The DishItems is a custom object and I am using FirebaseFireStoreSwift JSON decoder to retrieve the documents.
let db = Firestore.firestore().collection("Joints")
db.getDocuments { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No restaurants")
return
}
let jointsArray : [Joint] = documents.compactMap {
return try? $0.data(as: Joint.self)
}
completion(jointsArray)
}
I am able to query all of the documents if I exclude "menu" from the parent struct but as soon as I include it, then nothing is queried. How do I go about obtaining the array of objects inside the JSON?

Update an existing item in a ChatModel dictionary from a Firestore snapshot

I want to show an overview in a TableView of all existing chats (Name, LastMessage) for a specific user.
Right now, I append a new item in my TableView, which is obviously wrong, instead I want to update the existing item by it's key "ChatId"
This is my Model:
class ChatModel {
var chatOwnerId: String?
var chatPartnerId: String?
var createdDate: Timestamp?
var chatId: String?
var lastMessage: String?
var lastMessageDate: Timestamp?
init(dictionary: [String: Any]) {
chatOwnerId = dictionary["chatOwnerId"] as? String
chatPartnerId = dictionary["chatPartnerId"] as? String
createdDate = dictionary["createdDate"] as? Timestamp
chatId = dictionary["chatId"] as? String
lastMessage = dictionary["lastMessage"] as? String
lastMessageDate = dictionary["lastMessageDate"] as? Timestamp
}
}
How I add the data to my model:
func loadChatOverviewNew(currentUser: String) {
ChatApi.shared.observeChatOverviewNew(currentUser: currentUser) { (chatOverview) in
self.chats.insert(chatOverview, at: 0)
self.chatTableView.reloadData()
}
}
How can I update my "chats" when I receive a new snapshot, instead of appending / inserting it?
To update an existing item, find that item in the array by calling ``, and then update it.
Something like:
ChatApi.shared.observeChatOverviewNew(currentUser: currentUser) { (chatOverview) in
if let index = self.chats.firstIndex(where: { $0.chatId == chatOverview.chatId }) {
self.chats[index] = chatOverview
}
else {
self.chats.insert(chatOverview, at: 0)
}
self.chatTableView.reloadData()
}

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