How to read nested data from realtime database firebase
The main issue is the Firebase structure presented in the question doesn't match the code:
user_id
gas_station_id
name
and the code is attempting to read it like this
if let gasStationDict = snapshot.value as? [String:String]
Where the structure is more like this [String: [String: String]]
or possibly [String: [String: Int]]
I suggest another solution using .childSnapshot
to get deeply nested data. It's easier to read and way easier to maintain
I would also suggest changing the structure. In NoSQL databases it's often best practice to disassociate node keys from the data they contain.
For example, in your structure, 6642 is station ID. What if, in the future the station ID changes? You would literally have to go through your entire database, search for that, delete those nodes and whatever they contain and then re-write them. ugh!
Here's a better structure
{
"username123" : {
a_firebase_generated_node_key : { //using .childByAutoId to create
"latitude" : 37.861639,
"longitude" : -4.785556,
"name" : "CEPSA"
"station_id: "6652"
}
}
}
now you can change any aspect of the station and won't have to change any references to it.
Then the code to read and print all of the stations using childSnapshot
func printUsersGasStations() {
let ref = self.ref.child("gas_stations").child("username123")
ref.getData(completion: { error, snapshot in
if let err = error {
print(err.localizedDescription)
return
}
//this next line takes all of the stations and creates
// an ordered array of them as DataSnapshots
let allStationsSnap = snapshot.children.allObjects as! [DataSnapshot]
for stationSnap in allStationsSnap {
let stationId = stationSnap.childSnapshot(forPath: "station_id").value as? String ?? "No Station ID"
let lat = stationSnap.childSnapshot(forPath: "lat").value as? Double ?? 0.0
let lon = stationSnap.childSnapshot(forPath: "lon").value as? Double ?? 0.0
let name = stationSnap.childSnapshot(forPath: "name").value as? String ?? "No Name"
print(stationId, lat, lon, name)
}
})
}
Getting nested data in firebase using swift
here is the solution
let comments = messages.childSnapshot(forPath: "comments").value as? [String: Any] ?? [:]
for comment in comments {
let theComment = comment.value as? [String: Any]
let theContent = theComment?["content"] as? String ?? ""
let theIcon = theComment?["icon"] as? String ?? ""
let theColor = theComment?["color"] as? String ?? ""
let theDate = theComment?["date"] as? String ?? ""
let theName = theComment?["userName"] as? String ?? ""
let aComment = messageComments(content: theContent, color: theColor, icon: theIcon, date: theDate, userName: theName)
commentArray.append(aComment)
}
How to fetch nested data from Firebase realtime database by implementing observer in swift
Note: After writing this answer I realized it was way long but this is a big question and there are a lot of elements to address.
My first suggestion is to change the structure as it's overly complicated for what's being done with the data. Also, there is repetitive data that's not needed so that should be changed as well. For example, here's your profiles node
"profiles": {
"FTgzbZ9uWBTkiZK9kqLZaAIhEDv1": {
"fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
"name": "Skander",
"phoneNumber": "+95644125503",
"uid": "FTgzbZ9uWBTkiZK9kqLZaAIhEDv1" <- remove this, not needed.
},
As you can see, each child node has a key of the user id. But, you are also storing the user id as a child node as well. They key is the uid and will always be available so no need for duplication there and the child node should be removed.
Based on comments, this is a much better structure
/users
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1
"batteryStatus": 22,
"latitude": 40.9910537,
"longitude": 29.020425,
"timeStamp": 1556568633477,
"fcmToken": "fp09-Y9ZAkQ:APA91bFgGB1phr4B9gZScnz7ngpqTb5MchgWRFjHmLCVmWGMJVsyFx0rtrz7roxzpE_MmuSaMc4is-XIu7j718qjRVCSHY4PvbNjL1LZ-iytaeDP0oa8aJgE02wET3cXqKviIRMH",
"name": "Skander",
"phoneNumber": "+95644125503",
"conversationUid": "-L_w2yi8gh49GppDP3r5",
"friendStatus": "STATUS_ACCEPTED",
"notify": true,
"phoneNumber": "+915377588674",
and then, to keep track of a users friends, it becomes this
/userFriends
zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: true //their friend
IRoo0lbhaihioSSuFETngEEFEeoi: true //another friend
To load this users friends, we read the data at /userFriends/this_users_id and then iterate over the child nodes loading the data for display in the tableView
Lets start with an object that will be used to hold each friends data, and then an array that will be used as a tableView Datasource
class FriendClass {
var uid = ""
var name = ""
//var profilePic
//var batteryStatus
init(withSnapshot: DataSnapshot) {
self.uid = withSnapshot.key
self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
}
}
var myFriendsDataSource = [FriendClass]()
Then a functions to read the users node, iterate over the users friends uid's and read in each users data, populating the FriendClass object and storing each in an array. Note that self.ref points to my firebase.
func loadUsersFriends() {
let uid = "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
let myFriendsRef = self.ref.child("userFriends").child(uid)
myFriendsRef.observeSingleEvent(of: .value, with: { snapshot in
let uidArray = snapshot.children.allObjects as! [DataSnapshot]
for friendsUid in uidArray {
self.loadFriend(withUid: friendsUid.key)
}
})
}
func loadFriend(withUid: String) {
let thisUserRef = self.ref.child("users").child(withUid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let aFriend = FriendClass(withSnapshot: snapshot)
self.myFriendsDataSource.append(aFriend)
})
}
Now that we have the code to read in the data, you also want to watch for changes. There are a number of options but here's two.
1) I'll call this brute force.
Simply attach a .childChanged observer to the /users node and if something changes, that changed node is passed to the observer. If the key to that node matches a key in myFriendsDataSource array, update that user in the array. If no match, then ignore it.
func watchForChangesInMyFriends() {
let usersRef = self.ref.child("users")
usersRef.observe(.childChanged, with: { snapshot in
let key = snapshot.key
if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
let friend = self.myFriendsDataSource[friendIndex]
print("found user \(friend.name), updating")
//friend(updateWithSnapshot: snapshot) //leave this for you to code
}
})
}
2) Selective observing
For this, we simply attach an .childChanged observer to each friend node - and that can be done within the code example from above
func loadFriend(withUid: String) {
let thisUserRef = self.ref.child("users").child(withUid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let aFriend = FriendClass(withSnapshot: snapshot)
self.myFriendsDataSource.append(aFriend)
//add an observer to this friends node here.
})
}
One last thing: I didn't address this
"friendStatus": "STATUS_ACCEPTED",
I would think that only friends you accepted are in the friends list so the use is a tad unclear. However, if you want to use it you could do this
/userFriends
zzV6DQSXUyUkPHgENDbZ9EjXVBj2 //this user
FTgzbZ9uWBTkiZK9kqLZaAIhEDv1: "STATUS_ACCEPTED"
IRoo0lbhaihioSSuFETngEEFEeoi: "STATUS_DECLINED"
and then as you're itering over friends to load, ignore the ones that are declined.
If you MUST keep your current structure (which I do NOT recommend) the techniques in this answer will work for that structure as well, however, it will be a lot more code and you're going to be moving around a lot of unneeded extra data so the Firebase bill will be higher.
Firebase and reading nested data using Swift
var postsCommentsDict : NSMutableDictionary = NSMutableDictionary()
var userNameArray : [String] = [String]()
var userCommentArray : [String] = [String]()
FIRDatabase.database.reference().child("Posts").observeEventType(.Value, withBlock: {(Snapshot) in
if Snapshot.exists(){
let imageD = Snapshot.value
let imageD_ID = imageD.key
//Retrieving the email and image name
let imageName = imageD["userImage"] as! String
let userEmail = imageD["userEmail"] as! String
//Here you are accessing each image ID
//First Question Alternative Completed
//To answer the second Question :-
FIRDatabase.database.reference().child("Posts").child(imageD_ID).child("comments").observeEventType(.Value, withBlock: {(Snapshot) in
if let commentsDictionary = Snapshot.value {
for each in commentsDictionary{
postsCommentsDict.setObject(each["userName"] as! String , forKey : each["userComment"] as! String)
//Saving the userName : UserComment in a dictionary
userNameArray.append(each["userName"] as! String)
userCommentArray.append(each["userComment"] as! String)
//Saving the details in arrays
//Prefer dictionary over Arrays
}
} else {
//no comments to append the arrays
}
})
}
})
Once you are Done Saving the Comments dictionary : How to read it : -
for each in postsCommentsDict as! NSDictionary{
let userNm = each.key
let userComment = each.value
//username and userComment's retrieved
}
Please ignore the Typos, if any!...hope this helps..
Swift 4 / Firebase - Reading and storing double nested items from realtime databse in different arrays
Here's the complete code to read all of the data in the Live node, and put Today, Tomorrow and ThisWeek events into separate array's
var todayArray = [String]()
var tomorrowArray = [String]()
var thisWeekArray = [String]()
let liveRef = self.ref.child("Live")
liveRef.observeSingleEvent(of: .value, with: { snapshot in
let todaySnap = snapshot.childSnapshot(forPath: "Today")
for todayChild in todaySnap.children {
let todayChildSnap = todayChild as! DataSnapshot
let todayDict = todayChildSnap.value as! [String: Any]
let title = todayDict["title"] as! String
todayArray.append(title)
}
let tomorrowSnap = snapshot.childSnapshot(forPath: "Tomorrow")
for tomorrowChild in tomorrowSnap.children {
let tomorrowChildSnap = tomorrowChild as! DataSnapshot
let tomorrowDict = tomorrowChildSnap.value as! [String: Any]
let title = tomorrowDict["title"] as! String
tomorrowArray.append(title)
}
let thisWeekSnap = snapshot.childSnapshot(forPath: "ThisWeek")
for thisWeekChild in thisWeekSnap.children {
let thisWeekChildSnap = thisWeekChild as! DataSnapshot
let thisWeekDict = thisWeekChildSnap.value as! [String: Any]
let title = thisWeekDict["title"] as! String
thisWeekArray.append(title)
}
print("Today")
for today in todayArray {
print(" " + today)
}
print("Tomorrow")
for tomorrow in tomorrowArray {
print(" " + tomorrow)
}
print("This Week")
thisWeekArray.map { print(" " + $0) } //gettin' all swifty here
})
and the output is
Today
rnd
arnd
Tomorrow
brnd
This Week
drnd
crnd
However... I would probably change the stucture:
Live
event_0 //generated with childByAutoId
title: "rnd"
timestamp: "20180918"
event_1
title: "brnd"
timestamp: "20180919"
because you can now query on every node, pulling out today's events. Tomorrow's events or ThisWeeks event or any day or range you like.
I'm going to uploading new data each day so today, tomorrow and this
week will be refreshed each day
and with this structure, all you would need to do is add nodes and your queries will figure out what nodes are for what periods of time.
Since your Event class doesn't do any processing and essentially just a structure to hold your data, how about a struct:
struct EventStruct {
var EventTitle: String?
var EventDescription: String?
var EventStamp: Int?
}
Then add the events to the array:
let title = thisWeekDict["title"] as! String
let desc = thisWeekDict["desc"] as! String
let stamp = thsWeekDict["stamp"] as! String
let anEvent = Event(title: title, desc: desc, stamp: stamp)
thisWeekArray.append(anEvent)
or leave it as a class and just pass the snapshot in and have the class assign the properties from the snapshot. Either way, please add error checking for nil's.
Fetch nested array Firebase + SwiftUI
Please do yourself a favour and don't map data manually from Firestore to Swift.
Firestore supports the Swift Codable protocol, which makes things a lot easier.
Using Codable w/ Firestore
Here's a short snippet that shows how to adapt your code:
struct Order: Identifiable, Codable {
@DocumentID var id: String
var number: Int
var status: String
var clientName: String
// ...
}
class OrdersViewModel: ObservableObject {
@Published var orders = [Order]()
@Published var errorMessage: String?
private var db = Firestore.firestore()
private var listenerRegistration: ListenerRegistration?
public func unsubscribe() {
if listenerRegistration != nil {
listenerRegistration?.remove()
listenerRegistration = nil
}
}
func subscribe() {
if listenerRegistration == nil {
listenerRegistration = db.collection("orders")
.addSnapshotListener { [weak self] (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
self?.errorMessage = "No documents in 'orders' collection"
return
}
self?.orders = documents.compactMap { queryDocumentSnapshot in
let result = Result { try queryDocumentSnapshot.data(as: Order.self) }
switch result {
case .success(let order):
if let order = order {
// An Order value was successfully initialized from the DocumentSnapshot.
self?.errorMessage = nil
return order
}
else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
self?.errorMessage = "Document doesn't exist."
return nil
}
case .failure(let error):
// An Order value could not be initialized from the DocumentSnapshot.
switch error {
case DecodingError.typeMismatch(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.valueNotFound(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.keyNotFound(_, let context):
self?.errorMessage = "\(error.localizedDescription): \(context.debugDescription)"
case DecodingError.dataCorrupted(let key):
self?.errorMessage = "\(error.localizedDescription): \(key)"
default:
self?.errorMessage = "Error decoding document: \(error.localizedDescription)"
}
return nil
}
}
}
}
}
}
While this might look more complicated than your code, the key line is this:
try queryDocumentSnapshot.data(as: Order.self)
The rest of the code performs some error handling (which you should absolutely do), and thus adds a bit of noise.
Mapping arrays
Using Codable makes mapping arrays quite simple. You can use any type that is Codable
itself inside your arrays.
So for your order items, use the following:
struct OrderItem: Codable {
var cost: Int
var name: String
}
And then update the Order
struct as follows:
struct Order: Identifiable, Codable {
@DocumentID var id: String
var number: Int
var status: String
var clientName: String
var items: [OrderItem]
// ...
}
That's it - everything else will be handles automatically. See Mapping arrays in my article for further details.
Resources
For more details, please refer to Mapping Firestore Data in Swift - The Comprehensive Guide and SwiftUI: Mapping Firestore Documents using Swift Codable - Application Architecture for SwiftUI & Firebase. The article also explains how to customize the mapping using CodingKeys
.
Related Topics
Hmac Sha512 Using Commoncrypto in Swift 3.1
Play Mp4 Using Mpmovieplayercontroller() in Swift
Compile Latex Code Using Swift
Adding Animation to Tabviews in Swiftui When Switching Between Tabs
How to Capture Multiple Arguments Weakly in a Swift Closure
Firebase Crashlytics Not Showing Crash Report in Console Dashboard Swift
Manually Disposing a Disposebag in Rxswift
How to Trigger Xcode's 'Update to Latest Package Versions' from Command Line
Integer Literal Overflows When Stored into 'Int'
Swift Unit Testing with Xctassertthrows Analogue
How to Get the Scnvector3 Position of the Camera in Relation to It's Direction Arkit Swift
Swift: +[Catransaction Synchronize] Called Within Transaction While Decoding HTML Entities
Passing Dynamic Int Variable from One Class to Another Class in Swift
Any or a Trouble with Sequence of Functions