Firebase nested parsing Swift - ios

I have following Firebase query for parsing into my tableView
{
"-LC8fVE90ovEFRQAhhdv" : {
"description" : "Bu grupta arkadaşlar takılsınlar",
"members" : [ "VSVmIF4o0ie2QMIcXdTDTcC8Hq93", "hJgM5tYU45VmjaMTzYAG4Yo3RyD2", "YrLEyhWf9KOIwoK2YN7HrzxpwMc2" ],
"title" : "Takılmaca"
},
"-LC8wUR-11bX5FnjQ6Mk" : {
"description" : "Hello",
"members" : [ "VSVmIF4o0ie2QMIcXdTDTcC8Hq93", "hJgM5tYU45VmjaMTzYAG4Yo3RyD2", "nkwPkD2FeDZZFDnLunTe3D2EuQr2", "YrLEyhWf9KOIwoK2YN7HrzxpwMc2" ],
"messages" : {
"-LC9Pj9krCB4kZnXtXc8" : {
"content" : "Its me",
"senderId" : "YrLEyhWf9KOIwoK2YN7HrzxpwMc2"
},"-LC9Pj9krCB4kasfXtXc8" : {
"content" : "Who are you",
"senderId" : "YrLEyhWf9KOIwoK2YN7HrzxpwMc2"
}
}
I am trying to get content and senderId with following code
var groupMessages = [Message]()
REF_GROUPS.observeSingleEvent(of: .value) { (groups) in
guard let groups = groups.children.allObjects as? [DataSnapshot] else { return }
for group in groups {
let groupss = group.value as? NSDictionary
if let messages = groupss!["messages"] as? [String: Any] {
}
}
handler(groupMessages)
}
However, I could not reach content because of messages object is created by random identifiers (like LC9Pj9krCB4kZnXtXc8) how can I get that content and senderId?

First thing don't ever use NSDictionary or NSArray in Swift it makes the things complex. Secondly just check snapshot.exists() instead of putting a guard for allObjects.
for group in groups should be replace by for child in snapshot.children and the child will be a DataSnapshot itself. See below code:
REF_GROUPS.observeSingleEvent(of: DataEventType.value) { (snapshot) in
if snapshot.exists() {
var allMessages: Array<Dictionary<String, String>> = []
for child in snapshot.children {
let childSnapshot = child as! DataSnapshot
guard let dictValue = childSnapshot.value as? Dictionary<String, Any>, let messages = dictValue["messages"] as? Dictionary<String, Any> else {continue}
if let array = Array(messages.values) as? Array<Dictionary<String, String>> {
allMessages.append(contentsOf: array)
}
}
/// All messages into single array
print(allMessages)
/// All senderIds
let senderIds = allMessages.map({$0["senderId"]!})
print(senderIds)
/// Unique senderIds
let uniqueSenderIds = Array(Set(senderIds))
print(uniqueSenderIds)
/// Grouping the senderId with messages
var finalDict: Dictionary<String, [String]> = [:]
for data in allMessages {
guard let senderId = data["senderId"], let content = data["content"] else {continue}
if var oldValue = finalDict[senderId] {
oldValue.append(content)
}
else {
finalDict[senderId] = [content]
}
}
print(finalDict)
/// Inbuilt Dictionary grouping function
let dictionary = Dictionary(grouping: allMessages, by: {$0["senderId"]!})
print(dictionary)
}
Output: allMessages
[["content": "Its me", "senderId": "YrLEyhWf9KOIwoK2YN7HrzxpwMc2"],["content": "Who are you", "senderId": "YrLEyhWf9KOIwoK2YN7HrzxpwMc2"]]
Output: senderIds
["YrLEyhWf9KOIwoK2YN7HrzxpwMc2", "YrLEyhWf9KOIwoK2YN7HrzxpwMc2"]
Output: uniqueSenderIds
["YrLEyhWf9KOIwoK2YN7HrzxpwMc2"]
Output: finalDict
["YrLEyhWf9KOIwoK2YN7HrzxpwMc2": ["Its me", "Who are you"]]
Output: finalDict
["YrLEyhWf9KOIwoK2YN7HrzxpwMc2": [["content": "Its me", "senderId": "YrLEyhWf9KOIwoK2YN7HrzxpwMc2"], ["content": "Who are you", "senderId": "YrLEyhWf9KOIwoK2YN7HrzxpwMc2"]]]
I'm not sure why're you fetching all groups's content but I would like to suggest to get the data by Groups -> GroupId -> messages ref and put a listener for this ref so that app can be notify whenever new message will be received.

Related

How to grab Users personal list in Firebase to a tableview

I am trying to simply fetch a users favourite maps onto a tableview.
something that i thought would be very basic but turned out to be extremely difficult.
The code here is the best that i have managed so far, Attempting to somehow reference a (users id) with a (yourmaps id) to fetch specific information.
For example. Since the user has made 1 map his favourite(with id (-LpY4XEER-b21hwMi9sp)). I want to look through all maps within root["yourmap"] and only fetch his map onto a tableview.
Firebase
"users" {
"6g55cHXH4begwooHQvO4EKNV3xm1" : {
"photoURL" : "https://firebasestorage.googleap...",
"username" : "lbarri",
"yourmaps" : {
"-LpY4XEER-b21hwMi9sp" : true
}
}
}
"yourmaps": {
"-LpY4XEER-b21hwMi9sp" : {
"author" : {
"photoURL" : "https://firebasestorage.googleapis.com/v...",
"uid" : "6g55cHXH4begwooHQvO4EKNV3xm1",
"username" : "lbarri"
},
"mapmoderators" : {
"6g55cHXH4begwooHQvO4EKNV3xm1" : true
},
"mapphotoURL" : "https://firebasestorage.googleapis...",
"mapusername" : "Hello World"
},
"-LpYo_pQ8zIOGHHlNU1Q" : {
"author" : {
"photoURL" : "https://firebasestorage.googleapis.com/v...3",
"uid" : "RLFK9xnvhccTu2hbNHq0v05J2A13",
"username" : "lbarri"
},
"mapmoderators" : {
"RLFK9xnvhccTu2hbNHq0v05J2A13" : true
},
"mapphotoURL" : "https://firebasestorage.googleapis.com/...",
"mapusername" : "Dream"
}
}
Swift
func getCurrentUserMaps() {
guard let userProfile = UserService.currentUserProfile else { return }
let currentUserId = userProfile.uid
let userRef = Database.database().reference().child("users").child(currentUserId)
userRef.observeSingleEvent(of: .value, with: { (snapshot) in
let root = snapshot.value as? NSDictionary
if let mapsByUser = root!["yourmaps"] as? [String: Bool] {
for (documentId, status) in mapsByUser {
if status {
// Document is true, check for the maps
self.fetchyourmaps(key: documentId, owner: currentUserId)
}
}
}
}) { (error) in
print(error.localizedDescription)
}
}
func fetchyourmaps(key:String, owner:String) {
let yourMapRef = Database.database().reference().child("yourmaps")
yourMapRef.observeSingleEvent(of: .value, with: {snapshot in
let user = snapshot.value as? NSDictionary
if let mapsByUser = user!["mapmoderators"] as? [String: Bool] {
for (userId, status) in mapsByUser {
if userId == owner && status == true {
print("Owner \(owner) manages this \(user)")
var tempYourMap = [YourMapProfile]()
for key in (snapshot.value as? NSDictionary)! {
let childSnapshot = key as? DataSnapshot
let dict = childSnapshot!.value as? [String:AnyObject]
let author = dict!["author"] as? [String:AnyObject]
let uid = author!["uid"] as? String
let username = author!["username"] as? String
let photoURL = author!["photoURL"] as? String
let url = URL(string:photoURL!)
let mapusername = dict!["mapusername"] as? String
let mapphotoURL = dict!["mapphotoURL"] as? String
let mapurl = URL(string:mapphotoURL!)
let userProfile = UserProfile(uid: uid!, username: username!, photoURL: url!, mapPoints: mapPoints!)
let yourmapprofile = YourMapProfile(mapid: childSnapshot!.key as! String, mapauthor: userProfile, mapusername: mapusername!, mapphotoURL: mapurl!)
tempYourMap.append(yourmapprofile)
}
self.yourmaps = tempYourMap
self.tableView.reloadData()
}
}
}
})
}
print("Owner \(owner) manages this \(user)") does print the correct maps onto the console
After that line it is when i cant figure out how to package the information to my tableview.
I have searched everywhere for information on how to retrieve data from Firebase when referencing one root folder to another but i cant find anything helpful. So any link/guide/ tutorial etc would be appreciated and i'll gladly take it from there. Is this at least how you are supposed to do it?
There are a few ways to do this but here's two: Option 1 is to leverage a deep query to get the maps that are this users favorites. The second is to iterate over the users maps and pull each one at a time.
Option 1:
Start with a maps node like this
allMaps
map_0
favorite_of
uid_0: true
uid_3: true
map_user_name: "Larry"
map_1
favorite_of
uid_2: true
map_user_name: "Moe"
map_2
favorite_of
uid_0: true
map_user_name: "Curly"
Then, a deep query to get all the favorite maps of uid_0
func queryToGetMyFavoriteMaps() {
let uid = "uid_0"
let ref = self.ref.child("allMaps")
let path = "favorite_of/" + uid
let query = ref.queryOrdered(byChild: path).queryEqual(toValue: true)
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
print(child) //prints the map_0 & map_2 nodes since that's the favorite onces
}
})
}
Option 2
Change up the allMaps node since we won't be doing a query
allMaps
map_0
map_user_name: "Larry"
map_1
map_user_name: "Moe"
map_2
map_user_name: "Curly"
and then the users node will be something like this
users
uid_0
name: "Frank"
favorite_maps:
map_0: true
map_2: true
uid_1
name: "Leroy"
favorite_maps:
map_1: true
and then the code that reads uid_0's favorite_maps node, and gets the keys from that snapshot, and then iterates over them, reading the map nodes one at a time.
func iterateToGetFavoriteMaps() {
let uid = "uid_0"
let userRef = self.ref.child("users").child(uid)
userRef.observeSingleEvent(of: .value, with: { snapshot in
if let mapRefs = snapshot.childSnapshot(forPath: "favorite_maps").value as? [String: Any] {
let mapKeys = mapRefs.keys //note mapKeys is a Dict so we can directly access the keys
for key in mapKeys {
let mapRef = self.ref.child("allMaps").child(key)
mapRef.observeSingleEvent(of: .value, with: { mapSnapshot in
let name = mapSnapshot.childSnapshot(forPath: "mapUserName").value as? String ?? "No Name"
print(name)
})
}
}
})
}

Swift Firebase Save/Update multiple parents with the same child values

I have an array of strings which is the "uid's" of users. I am trying to append data/children to these multiple "uid's". Adding children or updating children to individual parents/users is easy and I understand how to do it. The problem is that this array can either contain 1 uid or 50 uid's. Is it possible for me to take these uid's and then update them with the same value? I am unsure what code to provide since I am just trying everything to attack this.
With the code below, this is me send a message to other users.
Array of uid strings
var data = [String]()
Sample code of me sending a message to 2 users, just wanted to provide something here to show I know how to update/save data
private func sendMessageWithProperties(_ properties: [String: Any]) {
let businessRef = Database.database().reference().child("Business Group Chats Messages").child((group?.uid)!).child((Auth.auth().currentUser?.uid)!)
let ref = Database.database().reference().child("Business Group Chats Messages").child((Auth.auth().currentUser?.uid)!).child((group?.businessName)!)
let businesChildRef = businessRef.childByAutoId()
let childRef = ref.childByAutoId()
let fromID = Auth.auth().currentUser!.uid
let timeStamp = Int(Date().timeIntervalSince1970)
var value:[String: Any] = ["fromId" : fromID, "timeStamp" : timeStamp, "name": self.loggedInUserData?["name"] as? String]
properties.forEach { (k,v) in
value[k] = v
}
childRef.updateChildValues(value) { (err, ref) in
if err != nil {
print(err!)
return
}
Database.database().reference().child("Business Group Chats").child((self.group?.uid)!).child((Auth.auth().currentUser?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp, "businessName":(self.group?.businessName)!])
Database.database().reference().child("Business Group Chats").child((Auth.auth().currentUser?.uid)!).child((self.group?.uid)!).updateChildValues(["last message" : childRef.key!, "timestamp" : timeStamp])
self.inputContainerView.inputTextField.text = nil
}
}
Here is me taking that array of "uid's" and then pulling and printing that I can access each "uid" through a array of strings. Allowing me to access, now I can append data to each.
Database.database().reference().child("Businesses").observe(.value, with: { snapshot in
if snapshot.exists() {
self.businessUID = snapshot.value as? NSDictionary
if let dict = snapshot.value as? NSDictionary {
for item in dict {
let json = JSON(item.value)
let businessUid = json["uid"].stringValue
for uid in self.data {
if uid == businessUid {
//print(uid)
self.businessessuids = uid
print(self.businessessuids)
Database.database().reference().child("Businesses").child(self.businessessuids).observe(.value, with: { snapshot in
print(snapshot)
print("Trying to pull data from multiple strings right here this shoudld work")
})
print("printing the values to match with the string UID's")
}
}
}
}
} else {
print("does not exist")
}
})

iterate over users in firebase database

I am trying to iterate over the users' information where and save it in an Object.
Here is my data
{
"users" : {
"ApC2wS444YbEEUt5BOpFjkn7YTD3" : {
"UserInfo" : {
"lat" : 123123,
"long" : 0,
"petAge" : 5,
"petNeme" : "zhshs"
}
},
"doRJjseSogNJrCQ55zMGlJwj6jh2" : {
"UserInfo" : {
"UID" : "doRJjseSogNJrCQ55zMGlJwj6jh2",
"lat" : 0,
"long" : 0,
"petAge" : 5,
"petName" : "BBB"
}
}
}
}
My code is like this
ref = Database.database().reference()
tableView.delegate = self
tableView.dataSource = self
let userID = Auth.auth().currentUser?.uid
ref = Database.database().reference()
self.ref.child("users").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
for child in snapshot.children.allObjects as! [DataSnapshot] {
print(child.value)
let value = snapshot.value as! NSDictionary
let userInfo = value[0] as! NSDictionary
let UID = userInfo["UID"] as! String
let lat = userInfo["lat"] as! Double
let long = userInfo["long"] as! Double
let name = userInfo["petName"] as! String ?? ""
let age = userInfo["petAge"] as! Double
}
}) { (error) in
print(error.localizedDescription)
}
I don't really understand how I should handle snapshots and how to get the data properly
I was able to a fetch a single node but when I try to pass iterate, it brings me back the same node with every iteration
Any help?
In your example you use in a for loop this let userInfo = value[0] as! NSDictionary which later gives you only the first node: [0].
Also let me show you my example of retrieving data from a snapshot (in my case I have cards with autoId):
func loadDataFromDb(completion: #escaping ([Card])->()) {
var cards: [Card] = []
let userRef = getCurrentUserRef()
usersRef.child(userRef).child(Paths.cards).observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let card = Card(snapshot: snapshot) {
cards.append(card)
}
}
completion(cards)
}
}
Also I have failable init in Card:
private enum Constants {
static let name = "name"
static let barcode = "barcode"
}
final class Card {
let name: String
let barcode: String
init(_ name: String, barcode: String) {
self.name = name
self.barcode = barcode
}
init?(snapshot: DataSnapshot) {
guard let value = snapshot.value as? [String : AnyObject],
let name = value[Constants.name] as? String,
let barcode = value[Constants.barcode] as? String else {
return nil
}
self.name = name
self.barcode = barcode
}
}

Access childAutoID to update selected child value in Firebase

In order to populate my tableView, I append items (created from a struct) to a local array:
func loadList() {
var newAnnotations: [AnnotationListItem] = []
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").queryOrderedByKey().observeSingleEvent(of: .value, with: {snapshot in
for item in snapshot.children {
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
annotationList = newAnnotations
self.tableView.reloadSections([0], with: .fade)
})
}
}
When I click a specific row, I am taken to a DetailViewController where it is only a large UITextView (named notes). The UITextView.text displayed is based on the selected indexPath.row and the "notes" value is retrieved from the array. Now the user is able to type some text and when they are done, the textViewDidEndEditing function is called:
func textViewDidEndEditing(_ textView: UITextView) {
notes.resignFirstResponder()
navigationItem.rightBarButtonItem = nil
let newNotes = self.notes.text
print(newNotes!)
}
Now I'd like to updateChildValues to newNotes to the child node "notes" in my JSON:
"users" : {
"gI5dKGOX7NZ5UBqeTdtu30Ze9wG3" : {
"annotations" : {
"-KuWIRBARv7osWr3XDZz" : {
"annotationSubtitle" : "1 Cupertino CA",
"annotationTitle" : "Apple Infinite Loop",
"notes" : "Does it work?!",
}
How can I access the selected autoID so I can update the specific notes node. So far the best I have is:
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(somehow access the specific childID).updateChildValues(["notes": newNotes])
Any help will be greatly appreciated. Thanks in advance
UPDATE
The annotationListItem struct is created:
struct AnnotationListItem {
let key: String?
var annotationTitle: String?
let annotationSubtitle: String?
let notes: String?
let ref: DatabaseReference?
init(key: String = "", annotationTitle: String, annotationSubtitle: String, notes: String) {
self.key = key
self.annotationTitle = annotationTitle
self.annotationSubtitle = annotationSubtitle
self.notes = notes
self.ref = nil
}
init(snapshot: DataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
annotationTitle = snapshotValue["annotationTitle"] as? String
annotationSubtitle = snapshotValue["annotationSubtitle"] as? String
notes = snapshotValue["notes"] as? String
ref = snapshot.ref
}
init(Dictionary: [String: AnyObject]) {
self.key = Dictionary["key"] as? String
self.annotationTitle = Dictionary["annotationTitle"] as? String
self.annotationSubtitle = Dictionary["annotationSubtitle"] as? String
self.notes = Dictionary["notes"] as? String
self.ref = nil
}
func toAnyObject() -> Any {
return [
"annotationTitle": annotationTitle as Any,
"annotationSubtitle": annotationSubtitle as Any,
"notes": notes as Any
]
}
}
UPDATE
This is how the annotationListItem is created to be stored in Firebase:
// Using the current user’s data, create a new AnnotationListItem that is not completed by default
let uid = Auth.auth().currentUser?.uid
guard let email = Auth.auth().currentUser?.email else { return }
let title = placemark.name
let subtitle = annotation.subtitle
let notes = ""
// declare variables
let annotationListItem = AnnotationListItem(
annotationTitle: title!,
annotationSubtitle: subtitle!,
notes: notes)
// Add the annotation under their UID
let userAnnotationItemRef = uidRef.child(uid!).child("annotations").childByAutoId()
userAnnotationItemRef.setValue(annotationListItem.toAnyObject())
I think you only need to do this:(since you have declared the note as global)
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(note.key).updateChildValues(["notes": newNotes])
inside the method where you change the notes
If I am not mistaken you are creating an array of a custom object?
var newAnnotations: [AnnotationListItem] = []
You could do something like: var newAnnotations: [(key: String, value: [String : Any])] = [] (Any only if you are going to have Strings, Integers, ect. If it'll only be String then specify it as a String.
Accessing the key would be: newAnnotations[indexPath.row].key in your cellForRowAtIndex of your tableView. Accessing values would be: newAnnotations[indexPath.row].value["NAME"].
You can have a separate array that holds the key and just append it at the same time as your population:
for item in snapshot.children {
guard let itemSnapshot = task as? FDataSnapshot else {
continue
}
let id = task.key //This is the ID
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
Another thing you could do is go up one more level in your firebase call:
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").observeSingleEvent(of: .value, with: {snapshot in
if snapshot is NSNull{
//Handles error
} else{
if let value = snapshot.value as? NSDictionary{ //(or [String: String]
//set localDictionary equal to value
}
}
self.tableView.reloadSections([0], with: .fade)
})
}
And then when you select a row: let selectedItem = localDictionary.allKeys[indexPath.row] as! String //This is the ID you pass to your viewController.

How to make multiple observations with Firebase?

I need to make multiple observations, but I don't know how.
Here is my database structure:
"Posts" : {
"f934f8j3f8" : {
"data" : "",
"date" : "",
"userid" : ""
}
},
"Users" : {
"BusWttqaf9bWP224EQ6lOEJezLO2" : {
"Country" : "",
"DOB" : "",
"Posts" : {
"f934f8j3f8" : true
},
"Profilepic" : "",
"name" : "",
"phonenumber" : ""
}
I want to observe the posts and I write the code and it works great, but I also want to get the name of the user who posted this post but when I wrote save the name and use it it gives me null. Here is my code.
DataServices.ds.REF_POSTS.queryOrderedByKey().observe(.value,
with: { (snapshot) in
self.posts = []
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let userID = "BusWttqaf9bWP224EQ6lOEJezLO2"
DataServices.ds.REF_USERS.child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let postusername = value?["name"] as? String ?? ""
})
print(" ------ User name : \(postusername) ------")
})
print(" ------ User name 2 : \(postusername) ------")
let post = Posts(postKey: key, postData: postsDict)
self.posts.append(post)
The first print statement prints the username, but the second one prints nothing.
Thanks in advance.
Firebase is asynchronous so you can't operate on a variable until Firebase populates it within it's closure. Additionally code is faster than the internet so any statements following a closure will occur before the statements within the closure.
The flow would be as follows
Query for the post {
get the user id from the post inside this closure
query for the user info {
create the post inside this second closure
append the data to the array inside this second closure
reload tableview etc inside this second closure
}
}
Something like this edited code
self.posts = []
myPostsRef.queryOrderedByKey().observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
if let postsDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let userID = "BusWttqaf9bWP224EQ6lOEJezLO2"
myUsersRef.child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let userName = value?["name"] as? String ?? ""
let post = Posts(postKey: key, postData: postsDict, name:userName)
self.posts.append(post)
})
}
}
}
})
You're not using the postusername inside the closure so I added that to the Posts initialization.
Also, the self.posts = [] is going to reset the posts array any time there's a change in the posts node - you may want to consider loading the array first, and then watch for adds, changes, or deletes and just update the posts array with single changes instead of reloading the entire array each time.
Edit:
A comment was made about the data not being available outside the loop. Here is a very simplified and tested version. Clicking button one populates the array from Firebase with a series of strings, clicking button 2 prints the array.
var posts = [String]()
func doButton1Action() {
let postsRef = ref.child("posts")
self.posts = []
postsRef.observe(.value, with: { (snapshot) in
if let snapshot = snapshot.children.allObjects as? [FIRDataSnapshot] {
for snap in snapshot {
let value = snap.value as! String
self.posts.append(value)
}
}
})
}
func doButton2Action() {
print(posts)
}

Resources