Fetch multi level node from Firebase - swift

I am trying to fetch the "friends" from the node to be able to show them in UICollectionView afterwards. I now realized that I have to use a struct and place the Friends array inside. I am struggling now to understand how to fetch them into that array (you can see it at the bottom of the post). Data is stored in a firebase node. How can I grab the data and what would be the procedure to place it in UICollectionView afterwards? This is my function so far to retrieve.
UPDATE: (I think I am fetching correctly now but I don't get any results. Is there something that I should do in collection view? or what am I doing wrong?)
UPDATE: Here is my code for post fetching:
func fetchPosts3() {
ref.child("Users_Posts").child("\(unique)").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
print(snapshot)
if snapshot.value as? [String : AnyObject] != nil {
let allPosts = snapshot.value as! [String : AnyObject]
self.posts.removeAll()
for (_, value) in allPosts {
if let postID = value["postID"] as? String,
let userIDDD = value["userID"] as? String
{
//ACCESS FRIENDS
ref.child("Users_Posts").child("\(unique)").child(postID).child("friends").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snap) in
print("FRIENDS: \(snap.childrenCount)")
//var routine = self.postsWithFriends[0].friends
for friendSnap in snap.children {
if let friendSnapshot = friendSnap as? DataSnapshot {
let friendDict = friendSnapshot.value as? [String: Any]
let friendName = friendDict?["name"] as? String
let friendPostID = friendDict?["postID"] as? String
let postsToShow = PostWithFriends(id: userIDDD, friends: [Friend(friendName: friendName!, friendPostID: friendPostID!)])
self.postsWithFriends.append(postsToShow)
print("COUNTING: \(self.postsWithFriends.count)")
// then do whatever you need with your friendOnPost
}
}
})
}
}
//GET LOCATION
self.collectionView?.reloadData()
self.posts.sort(by: {$0.intervalPosts! > $1.intervalPosts!})
}
})
ref.removeAllObservers()
}
That's how the data looks at the database:
{
"-LN2rl2414KAISO_qcK_" : {
"cellID" : "2",
"city" : "Reading",
"date" : "2018-09-23 00:41:26 +0000",
"friends" : {
"UJDB35HDTIdssCtZfEsMbDDmBYw2" : {
"name" : "Natalia",
"postID" : "-LN2rl2414KAISO_qcK_",
"userID" : "UJDB35HDTIdssCtZfEsMbDDmBYw2"
},
"Vyobk7hJu5OGzOe7E1fcYTbMvVI2" : {
"name" : "Gina C",
"postID" : "-LN2rl2414KAISO_qcK_",
"userID" : "Vyobk7hJu5OGzOe7E1fcYTbMvVI2"
}
},
}
}
And this is my object that's stored into array
struct PostWithFriends {
var postID : String?
var friends: [Friend]
}
class Friend : NSObject {
var friendName: String?
var friendUserID: String?
var postID: String?
init(friendName: String, friendPostID: String) {
self.friendName = friendName
self.postID = friendPostID
}
}

Replace this
if let friend = snap.value as? [String : AnyObject] {
}
With this:
for friendSnap in snap.children {
if let friendSnapshot = friendSnap as? FIRDataSnapshot {
let friendOnPost = FriendOnPost()
let friendDict = friendSnapshot.value as? [String: Any]
friendOnPost.name = friendDict?["name"] as? String
friendOnPost.friendUserID = friendDict?["userID"] as? String
friendOnPost.postID = friendDict?["postID"] as? String
// then do whatever you need with your friendOnPost
}
}

Related

Firebase items returning multiple times in collection view on database change

When the function to create or delete a list is called inside of my app the remaining lists are duplicated and displayed multiple times within the collectionView until the app is reloaded. I only called the fetchLists function twice, in the viewDidLoad and in the pull to refresh function. On pull to refresh the lists return to normal.
Fetch list:
fileprivate func fetchLists() {
self.collectionView?.refreshControl?.endRefreshing()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("list-feed").child(currentUid)
ref.observe(.value) { (listFeedSnapshot) in
guard var allObjects = listFeedSnapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (allObjectsSnapshot) in
let listId = allObjectsSnapshot.key
let listRef = Database.database().reference().child("lists").child(listId)
listRef.observeSingleEvent(of: .value, with: { (snapshot) in
guard let dict = snapshot.value as? [String: Any] else { return }
guard let uid = dict["uid"] as? String else { return }
Database.fetchUserWithUID(uid: uid, completion: { (user) in
guard let dictionary = snapshot.value as? [String: Any] else { return }
var list = List(user: user, dictionary: dictionary)
let listId = snapshot.key
list.id = snapshot.key
self.list = list
self.lists.append(list)
self.lists.sort(by: { (list1, list2) -> Bool in
return list1.creationDate.compare(list2.creationDate) == .orderedDescending
})
self.collectionView?.reloadData()
ref.keepSynced(true)
listRef.keepSynced(true)
})
})
})
}
}
Create list:
let values = ["uid": uid, "title": listNameText, "creationDate": Date().timeIntervalSince1970] as [String : Any]
ref.updateChildValues(values, withCompletionBlock: { (error, ref) in
if let error = error {
self.navigationItem.rightBarButtonItem?.isEnabled = true
print("failed to save user info into db:", error.localizedDescription)
return
}
let memberValues = [uid : 1]
ref.child("list-members").updateChildValues(memberValues)
self.handleUpdateFeeds(with: ref.key!)
self.handleListFeeds(with: ref.key!)
print("successfully created list in db")
Update feeds:
func handleUpdateFeeds(with listId: String) {
guard let uid = Auth.auth().currentUser?.uid else { return }
let values = [listId: 1]
Database.database().reference().child("list-feed").child(uid).updateChildValues(values)
}
func handleListFeeds(with listId: String) {
guard let uid = Auth.auth().currentUser?.uid else { return }
let values = [listId: 1]
Database.database().reference().child("user-lists").child(uid).updateChildValues(values)
}
Firebase database:
{
"list-feed" : {
"otxFDz0FNbVPpLN27DYBQVP4e403" : {
"-LjeAoHJTrYK7xjwcpJ9" : 1,
"-LjeApq-Mb_d_lAz-ylL" : 1
}
},
"lists" : {
"-LjeAoHJTrYK7xjwcpJ9" : {
"creationDate" : 1.5630020966384912E9,
"title" : "Test 1",
"uid" : "otxFDz0FNbVPpLN27DYBQVP4e403"
},
"-LjeApq-Mb_d_lAz-ylL" : {
"creationDate" : 1.563002101329072E9,
"list-members" : {
"otxFDz0FNbVPpLN27DYBQVP4e403" : 1
},
"title" : "Test 2",
"uid" : "otxFDz0FNbVPpLN27DYBQVP4e403"
}
}
}
Since you're calling ref.observe(, you're attaching a permanent observer to the data. This means that if you call fetchLists a second, you're attaching a second observer and you'll get the same data twice.
If you only want the data to be read once per call to fetchLists, you should use observeSingleEventOfType:
fileprivate func fetchLists() {
self.collectionView?.refreshControl?.endRefreshing()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
let ref = Database.database().reference().child("list-feed").child(currentUid)
ref.observeSingleEvent(of: .value) { (listFeedSnapshot) in
Also see the documentation on reading data once.

How to get an array field from Firestore and write in struct field in Swift?

The problem that I faced is, I can reach every field in Firestore and write in structs except array&map field.
My firestore data is something like:
let data : [String : Any] = [
"name" : "House A",
"price" : 2000,
"contents" : [
"water" : true,
"internet" : false
]
]
Here is getDocument function:
let docRef = db.collection("example").document("example")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let data = Houses(Doc: document)
...
...
...
} else {
print(error, "Item not found")
}
}
Here is my structs :
struct Houses {
var name: String?
var price: Int
var contents : Contents
init(Doc: DocumentSnapshot){
self.name = Doc.get("name") as? String ?? ""
self.price = Doc.get("price") as! Int
self.contents = Doc.get("contents") as! Contents
}
}
struct Contents {
var water: Bool
var internet : Bool
init?(data: [String: Any]) {
guard let water = data["water"] as? Bool,
let internet = data["internet"] as? Bool else {
return nil
}
self.water = water
self.internet = internet
}
}
The other version of Contents :
struct Contents {
var water: Bool
var internet : Bool
init(Doc: DocumentSnapshot){
self.water = Doc.get("water") as! Bool
self.internet = Doc.get("internet") as! Bool
}
}
UPDATED
The problem solved with changing this line:
self.contents = Doc.get("contents") as! Contents
to;
self.contents = Contents(data: Doc.get("contents") as! [String : Any])
name and price returns what I expected but contents always return nil. I tried to configure Contents but results are same. I think, I have to configure struct named Contents.
Any help would be appreciated.

How to map the nested data in a document from Firestore by Swift?

I have a document data structure on Firestore like this:
pic1
pic2
So there are 2 map-objects inside the document and a collection and a another document inside this document
Then I create 3 model swift files for this document
task:
struct task {
var Name: String
var Address: String
var Car: CarModel
car Price: PriceModel
var dictionary: [String:Any] {
return [
"Name" : Name,
"Address" : Address,
"Car" : CarModel,
"Price" : PriceModel
]
}
init?(data: [String:Any]) {
guard let Name = dictionary["Name"] as? String,
let Address = dictionary["Address"] as? String,
let Car = ditionary["car"] as? CarModel,
let Price = dictionary["price"] as? PriceModel else{
return nil
}
self.Name = Name
self.Address = Address
self.Car = Car
self.Price = Price
}
}
CarModel:
struct CarModel {
var brand: String
var model: String
var year: String
var dictionary: [String:Any] {
return [
"brand" : brand,
"model" : model,
"year" : year,
]
}
init?(data: [String:Any]) {
guard let brand = dictionary["brand"] as? String,
let model = dictionary["model"] as? String,
let year = ditionary["year"] as? String else{
return nil
}
self.brand = brand
self.model = model
self.year = year
}
}
PriceModel:
struct PriceModel {
var basic: Int
var extra: Int
var dictionary: [String:Any] {
return [
"basic" : basic,
"extra" : extra,
]
}
init?(data: [String:Any]) {
guard let basic = dictionary["basic"] as? Int,
let extra = ditionary["extra"] as? Int else{
return nil
}
self.basic = basic
self.extra = extra
}
}
Then download the data with this following code:
func loadDataFromFirestore(completion: #escaping (Bool) -> ()) {
var success: Bool = false
DispatchQueue.global(qos: .userInteractive).async {
let downloadGroup = DispatchGroup()
let colRef = db.collection("tasks")
downloadGroup.enter()
colRef.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error: \(error)")
return
}
for document in querySnapshot!.documents{
let result = document.data()
print (result)
if let data = task(data: result){
print(data)
}
}
success = true
downloadGroup.leave()
}
downloadGroup.wait()
DispatchQueue.main.async {
completion(success)
}
}
}
I can get the data with comment the CarModel and PriceModel, but if I uncomment these two, it will let my app crash!
So how could I get the map-object to adapt to my code?
And the second question is: How can I get the document inside a document's collection with this kind of code?

Having trouble trying to obtain value from Firebase

I'm trying to get a value inside a Firebase database I set up but I'm having issues trying get it out. There is a section in my program where I go through each child under Cities and obtain the the name of each city. I thought it would be a good idea to try to obtain the status for each city in the same section but I can't get it out. Below is a sample of the JSON object. I feel like I'm close but missing something to put it all together.
"Users" : {
"NPNBig20BXNpX4Rz0UbMyiKAarY2" : {
"Cities" : {
"New York City" : {
"Status" : "Empty"
},
"Atlanta" : {
"Status" : "Empty"
},
"Test City" : {
"Status" : "Wow",
"Value" : "Test"
}
},
"Email" : "fakeemail#gmail.com"
}
}
Code:
guard let uid = userID else { return }
let databaseRef = Database.database().reference(fromURL: "https://testApp.firebaseio.com/").child("Users").child(uid).child("Cities")
var dataTest : [String] = []
//var cityDictionary: [String:String]()
databaseRef.observeSingleEvent(of: .value, with: {(snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
guard let value = snap.value else { return }
//let testData = value["Status"] **Type Any has no subscript
print("Key: ", key, "\nValue: ", value)
dataTest.append(key)
}
completion(dataTest)
})
This is the printed output
Key: New York City
Value: {
Status = Empty;
}
Key: Sintra
Value: {
Status = Empty;
}
Key: Test City
Value: {
Status = Wow;
Value = Test;
}
Here is the way you can get Status from your value:
if let value = snap.value as? [String: AnyObject] {
let Status = value["Status"] as? String ?? ""
}
And your complete code will be:
guard let uid = userID else { return }
let databaseRef = Database.database().reference(fromURL: "https://testApp.firebaseio.com/").child("Users").child(uid).child("Cities")
var dataTest : [String] = []
//var cityDictionary: [String:String]()
databaseRef.observeSingleEvent(of: .value, with: {(snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
guard let value = snap.value else { return }
//let testData = value["Status"] **Type Any has no subscript
if let value = snap.value as? [String: AnyObject] {
let Status = value["Status"] as? String ?? ""
}
print("Key: ", key, "\nValue: ", value)
dataTest.append(key)
}
completion(dataTest)
})
func handleGetCities(_ completion: #escaping ([String]) -> ()) {
guard let uid = Auth.auth().currentUser?.uid else { return }
// OR
guard let uid = userID else { return }
// THEN
var data: [String] = []
let reference = Database.database().reference()
reference.child("Users").child(uid).child("Cities").observe(.childAdded, with: { (snapshot) in
let city_name = snapshot.key
data.append(city_name)
completion(data)
}, withCancel: nil)
}

Retrieve / read data from Firebase database

I am using this code to retrieve data from a Firebase database. However, I am only able to pull one variable at a time. Can you please help me find a way to pull the others (student names, title etc.) and save them to an object arrayList?
{
"projects" : [
1, {
"answer1" : false,
"answer2" : true,
"answer3" : true,
"campus" : "Science Academy",
"category" : "LifeScience",
"question1" : "This is question 1",
"question2" : "This is question 2",
"question3" : "This is question 3",
"student1" : "john",
"student2" : "Kyle",
"title" : "Amazon Forest"
}
]
}
var ref : DatabaseReference?
var handle : DatabaseHandle?
ref = Database.database().reference()
handle = ref?.child("projects").child("1").child(question1).observe(.value, with: { (snapshot) in
if let question = snapshot.value as? String {
print(question)
}
})
IF your database has more that just projects/1 and you're wanting to access projects/1-2-3-4 etc. then you'll want to do something like this:
let reference = Database.database().reference()
reference.child("projects").observe(.childAdded, with: { (snapshot) in
let key = snapshot.key // THIS WILL GET THE PROJECT THAT IT'S IN. 1, 2, 3, 4 etc.
guard let dictionary = snapshot.value as? [String: AnyObject] else { return }
let answer1 = dictionary["answer1"] as? Bool
let campus = dictionary["campus"] as? String
}, withCancel: nil)
IF you're wanting to simple get a hold of all the values inside of project/1 and not just the questions, you'll want to do something like this:
let reference = Database.database().reference()
reference.child("projects").child("1").observeSingleEvent(of: .value, with: { (snapshot) in
let key = snapshot.key // THIS WILL GET THE PROJECT THAT IT'S IN. 1, 2, 3, 4 etc.
guard let dictionary = snapshot.value as? [String: AnyObject] else { return }
let answer1 = dictionary["answer1"] as? Bool
let campus = dictionary["campus"] as? String
}, withCancel: nil)