how to read firebase database - swift

This is my data structure: many clubs, each club has address. I tried to make the database flat.
Now I want to load a few club info on table view. When I swipe down iPhone screen, it will load next a few club info.
This is my code. But it loads all club info. How can I load only a few club, and load next a few club when user swipe down?
func loadClubs() {
ref = Database.database().reference()
ref.child("club").observe(DataEventType.value, with: { (snapshot) in
//print("clubs: \(snapshot)")
let array:NSArray = snapshot.children.allObjects as NSArray
for obj in array {
let snapshot:DataSnapshot = obj as! DataSnapshot
if let childSnapshot = snapshot.value as? [String: AnyObject] {
if let clubName = childSnapshot["name"] as? String {
print(clubName)
}
}
}
})
}

Firebase's queries support pagination, but it's slightly different from what you're used to. Instead of working with offsets, Firebase uses so-called anchor values to determine where to start.
Getting the items for the first page is easy, you just specify a limit to the number of items to retrieve:
ref = Database.database().reference()
query = ref.child("club").queryOrderedByKey().limitToFirst(10)
query.observe(DataEventType.value, with: { (snapshot) in
Now within the block, keep track of the key of the last item you've shown to the user:
for obj in array {
let snapshot:DataSnapshot = obj as! DataSnapshot
if let childSnapshot = snapshot.value as? [String: AnyObject] {
lastKey = childSnapshot.key
if let clubName = childSnapshot["name"] as? String {
print(clubName)
}
}
}
Then to get the next page, construct a query that starts at the last key you've seen:
query = ref.child("club").queryOrderedByKey().startAt(lastKey).limitToFirst(11)
You'll need to retrieve one more item than your page size, since the anchor item is retrieve in both pages.

i think the right way make reference to array of elements,
and make variable for index
var i = 0;
var club = null;
club = loadClubs(index); // here should return club with specified index;
// and increment index in loadClubs func,
// also if you need steped back --- and one more
// argument to function, for example
// loadClubs(index, forward) // where forward is
// boolean that says in which way we should
// increment or decrement our index
so in your example will be something like this:
func loadClubs(index, forward) {
ref = Database.database().reference()
ref.child("club").observe(data => {
var club = data[index];
if(forward){
index ++
}else{
index--
}
return club;
})
}

Related

How to save data to a specific uid for a specific logged in user in swift/firebase

I'm trying to allow a user to save data to a tableView using an alert that transfer data from the View Controller that the alert is in (CreatePlaylistVC) to another ViewController(CreatedPlaylistVC) that the tableView is in, saving for each specific account for a specific uid.
I've tried setting the value to the uid but this did work for me although it did save to the database under that specific uid.
CreatePlaylistVC
ref = Database.database().reference()
alert.addAction(UIAlertAction(title:"OK", style:.default, handler: {
action in
if let playlistName = alert.textFields?.first?.text {
let userID = Auth.auth().currentUser?.uid
self.ref?.child("PlaylistName").child(userID!).setValue(playlistName)
CreatedPlaylistVC
var ref:DatabaseReference?
var databaseHandle:DatabaseHandle?
override func viewDidLoad() {
//Set the firebase reference
ref = Database.database().reference()
//Retrieve the posts and listen fro changes
databaseHandle = ref?.child("PlaylistName").observe(.childAdded, with: { (snapshot) in
//Try to covert the value of the data to a string
let post = snapshot.value as? String
if let actualPost = post {
//Append the data to our playlistNameArray
self.playlistNameArray.append(actualPost)
//Reload the tableView
self.tableView.reloadData()
}
})
}
The expected results is to save the data only for the specified uid or currently logged in user. But it is saving for each user even though in the database it is saved to the right uid.
When using .childAdded for observe, it will go through every existing child under "PlaylistName" which in this case will be every user that has saved something.
Might have to reconsider your structure. Or use childByAutoID.
Edit: To use child by auto ID
// Your ["Name": Playlist] will get nested into an autogenerated child
self.ref?.child("PlaylistName").child(userID!).childByAutoID().setValue(["Name":playlistName])
//You will be listening for any new additions that your current user has made
databaseHandle = ref?.child("PlaylistName").child(Auth.auth().currentUser!.uid).observe(.childAdded, with: { (snapshot) in
// this loops through a list of playlist names your user creates
for child in snapshot.children{
let snap = child as! DataSnapshot
let value = snap.value as? Dict<String,Any>
let post = value["Name"] as! String
if let actualPost = post {
self.playlistNameArray.append(actualPost)
self.tableView.reloadData()
}
}
})
But my suggestion, if it suits your application, would be to change your data structure because it can keep your structure a lot flatter.
self.ref?.child(userID!).child("PlaylistName").setValue(playListName)
databaseHandle = ref?.child(userID!).observe(.childAdded, with: { (snapshot) in
let value = snapshot.value as? Dict<String,Any>
let post = value["PlaylistName"]
if let actualPost = post {
self.playlistNameArray.append(actualPost)
self.tableView.reloadData()
}
})

Order tableviewcell by child timestamp

I tried with this code to sort my posts by timestamp it doesn't work, each time I launch the simulator the order of the cells is different, I suppose this isn't the way to do it, could somebody explain me where I am wrong...
I edited the code, now my problem is that the most recent posts are displayed at the bottom and I would like them to to be displayed at the top
self.user.removeAll()
for child in DataSnapshot.children.allObjects as! [DataSnapshot] {
print("Processing user \(child.key)")
let value = child.value as? NSDictionary
//if country == "UNITED STATES"{
if let uid = value?["userID"] as? String{
if uid != Auth.auth().currentUser!.uid {
//
let userToShow = User()
if let fullName = value?["username"] as? String , let imagePath = value?["photoURL"] as? String{
userToShow.username = fullName
userToShow.imagePath = imagePath
userToShow.userID = uid
self.user.append(userToShow)
}
}
}
}
As soon as you call DataSnapshot.value, you're converting the data in the snapshot into a dictionary. And the order if keys in that dictionary is not guaranteed.
To maintain the order of the elements as they come back from the database, you need to loop over DataSnapshot.children. See these questions for examples of how to do that:
Iterate over snapshot children in Firebase
post on the firebase-talk mailing list
For your code this would look something like:
ref.child("users").queryOrdered(byChild: "timestamp").observeSingleEvent(of: .value, with: { snapshot in
self.user.removeAll()
for child in snapshot.children.allObjects as [DataSnapshot] {
print("Processing user \(child.key)")
let value = child.value as? NSDictionary
if let uid = value["userID"] as? String {
...
}
}
self.tableview.reloadData()
})

Problem Accessing Items Within NSDictionary Swift

I'm a newb to Swift programming, but experience in other languages.
I am having problem accessing items within NSDictionary to build out view elements. This is coming back from a Firebase instance.
Can someone take a look at the code and the output and lead me in the right direction to access these object properties?
ref.observe(.value, with: { (snapshot) in
for child in snapshot.children { //even though there is only 1 child
let snap = child as! DataSnapshot
let dict = snap.value as? NSDictionary
for (joke, item) in dict ?? [:] {
print(joke)
print(item)
}
}
})
This is the output from the print() methods.
joke2
{
PostUser = "Bobby D";
Punchline = "His money went to the movies.";
Rating = 1;
Setup = "Why did the dad go hungry?";
}
joke
{
PostUser = "Billy G";
Punchline = "Because he couldn't moo to a job.";
Rating = 3;
Setup = "Why did the cow go to school?";
}
Can someone tell me how to create items from these objects? Something like:
var posterName = joke.PostUser
When I try this, I get the error Value of type 'Any' has no member 'PostUser'. I've tried to access these DB object properties in multiple different ways described on SO and can't get any further.
I would recommend you to convert the output into objects like this:
struct Item {
var postUser: String?
var punchline: String?
var rating: Int?
var setup: String?
init(fromDict dict: [String: AnyObject] ) {
self.postUser = dict["PostUser"] as? String
self.punchline = dict["Punchline"] as? String
self.rating = dict["Rating"] as? Int
self.setup = dict["Setup"] as? String
}
}
And use it like this:
ref.observe(.value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
guard let dict = snap.value as? [String: AnyObject] else { continue }
let myItem = Item(fromDict: dict)
print(myItem)
}
})
But you could also access items in your dictionary directly like this:
let posterName = joke["PostUser"] as? String

Getting values from Firebase snapshot in Swift

Im successfully getting data from Firebase but I can't manage to push it into array to use. My database is as follows:
users
-Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
-email : "mike#gmail.com"
-lists
-LJiezOzfDrqmd-hnoH-
-owner: Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
-LJif-UgPgbdGSHYgjY6
-owner: Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2
shopping-lists
-LJh6sdBJtBCM7DwxPRy
-name: "weekly shopping"
-owner: "mike#gmail.com"
I have a home page after login that shows existing shopping lists on table if they exist. On viewDidLoad() I get shopping list IDs from the user and use those IDs as a reference to get details from shopping-lists.
However, I cant manage to save these data into an array as it gets deleted after closure. How can I do that in a clean way?
override func viewDidLoad() {
super.viewDidLoad()
SVProgressHUD.show()
tableView.allowsMultipleSelectionDuringEditing = false
// Sets user variable - must have
Auth.auth().addStateDidChangeListener { auth, user in
guard let user = user else { return }
self.user = User(authData: user)
// If new user, write into Firebase
self.usersRef.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.hasChild(self.user.uid) {
self.usersRef.child(user.uid).setValue(["email": user.email!])
}
})
// Get shopping lists data from "users/lists"
self.usersRef.child(user.uid).child("lists").observe(.value, with: { snapshot in
// Get list IDs
if snapshot.exists() {
if let result = snapshot.children.allObjects as? [DataSnapshot] {
for child in result {
self.listNames.append(child.key)
}
}
}
// Use list IDs - to get details
for item in self.listNames {
let itemRef = self.shoppingListsRef.child(item)
itemRef.observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
let name = value["name"] as? String ?? ""
let owner = value["owner"] as? String ?? ""
let shoppingList = ShoppingList(name: name, owner: owner)
self.items.append(shoppingList)
}
})
}
})
self.tableView.reloadData()
SVProgressHUD.dismiss()
}
}
(the question is a bit unclear so several parts to this answer to cover all possibilities. This is Swift 4, Firebase 4/5)
You don't really need to query here since you know which nodes you want by their key and they will always be read in the in order of your listNames array. This assumes self.listNames are the keys you want to read in.
for item in listNames {
let itemRef = shoppingListsRef.child(item)
itemRef.observe(.value, with: { (snapshot) in
if let value = snapshot.value as? [String: Any] {
let name = value["name"] as? String ?? ""
let owner = value["owner"] as? String ?? ""
print(name, owner)
}
})
}
Generally, queries are used when you are searching for something within a node - for example if you were looking for the node that contained a child name of 'weekly shopping'. Other than that, stick with just reading the nodes directly as it's faster and has less overhead. Keep reading...
I also removed the older NSDictionary and went with the Swift [String: Any] and modified your error checking
However, the real issue is reading that node with an .observe by .value. Remember that .value reads in all children of the node and then the children need to be iterated over to get each separate DataSnapshot. Also, .observe leaves an observer on the node notifying the app of changes, which I don't think you want. So this will answer the question as posted, (and needs better error checking)
for item in listNames {
let queryRef = shoppingListsRef
.queryOrdered(byChild: "name")
.queryEqual(toValue: item)
queryRef.observe(.value, with: { (snapshot) in
for child in snapshot.children { //even though there is only 1 child
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let name = dict["name"] as? String ?? ""
let owner = dict["owner"] as? String ?? ""
print(name, owner)
}
})
}
And the answer...
This is probably more what you want...
for item in listNames {
let queryRef = shoppingListsRef
.queryOrdered(byChild: "name")
.queryEqual(toValue: item)
queryRef.observeSingleEvent(of: .childAdded, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let name = dict["name"] as? String ?? ""
let owner = dict["owner"] as? String ?? ""
print(name, owner)
})
}
note the .childAdded instead of .value which presents the snapshot as a single DataSnapshot and doesn't need to be iterated over and the .observeSingleEvent which does not leave an observer attached to each node.
Edit
Based on additonal information, it would be best too change the structure to this
shopping-lists
-LJh6sdBJtBCM7DwxPRy
-name: "weekly shopping"
-uid: "Wc1EtcYzZSMPCtWZ8wRb8RzNXqg2"
and then when the user logs in just query the shopping lists node for any uid that's theirs.

Firebase paginating data returns same data?

Currently, I am initially loading the user's messages through :
func fetchMessages() {
if started == true {
let messageRef = Database.database().reference().child("messages").queryOrdered(byChild: "convoID").queryEqual(toValue: convoID).queryLimited(toLast: 10)
messageRef.observe(.childAdded) { (snapshot) in
if let value = snapshot.value as? NSDictionary {
let newMessage = message()
newMessage.messageText = value["content"] as? String
newMessage.sender = value["sender"] as? String
newMessage.messageID = snapshot.key
self.messageList.append(newMessage)
self.queryingStatus = true
self.messagesTableView.reloadData()
self.scrollToBottom()
}
}
}
}
Now, to minimize the data download, I decided to break the messages into chunks as such so that the user will download ten subsequent messages each time they pull up on the table view:
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
let lastIDDictionary = messageList[0]
let lastIDQueried = lastIDDictionary.messageID
let messageRefAddition = Database.database().reference().child("messages").queryOrdered(byChild: "convoID").queryLimited(toLast: 10).queryEnding(atValue: convoID, childKey: lastIDQueried!)
messageRefAddition.observeSingleEvent(of: .value) { (snapshot) in
for child in snapshot.children.allObjects as! [DataSnapshot] {
if let messageValue = child.value as? NSDictionary {
let newMessage = message()
newMessage.messageText = messageValue["content"] as? String
newMessage.sender = messageValue["sender"] as? String
newMessage.messageID = child.key
self.messageList.insert(newMessage, at: 0)
self.messagesTableView.reloadData()
}
}
}
refreshControl.endRefreshing()
}
The problem is, when I pull up on the table view, the first time it returns some new messages (I am not sure whether the order is even correct). However, when I pull on the table view again to refresh, it adds those same ten messages again. I printed the lastIDQueried in the refresh method, and after the initial load the ID remains the same even though I am accessing the first item in the array of dictionaries? Basically,when I refresh the table view, it is not querying the correct data and my pagination implementation does not seem to be working correctly.
Basically, the problem was that I was inserting the post in the wrong place in the array and the last item was still being added to the array which was always the same (as I ended on the value). As such, I added a counter that incremented each time a value was added. Then, I inserted the subsequent post at the counter value in the array then again incremented. Finally, if the message ID was equal to the current first message in array, I would not insert it.