Data inside Firebase Database read twice with an observeSingleEvent - swift

It's been now several hours I'm trying to dig Internet forums to try to find a solution to my problem and I can't figure out what's happening so I hope someone here can help)
I have a swift app and on one of my viewcontrollers, what I would want to do is to retrieve data from Firebase database and display them on my tableview.
Here you can see how my data are organized. Firebase org of data
To get the number of rows in my tableview, I put this into the viewWillAppear section
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Count the number contacts to get the correct number of lines in the table
let ref_table = ref.child("users").child((user?.uid)!).child("emergencyContacts")
print("Starting observing")
ref_table.observe(.value, with: { (snapshot: DataSnapshot!) in
print("Got snapshot")
print(snapshot.childrenCount)
let count = snapshot.childrenCount
self.numberOfContact = Int(count)
self.UI_contactsTableView.reloadData()
ref_table.removeAllObservers()
})
print("Returning count")
}
Then, to get the number of lines, I have this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Number of contact TV: \(numberOfContact)")
return numberOfContact
}
And to fill the cells of my tableview
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contact-cell") as! EmergencyContactCell
print("value of numberOfContact \(numberOfContact)")
ref.child("users").child((user?.uid)!).child("emergencyContacts").observeSingleEvent(of: .value, with: { (snapshot: DataSnapshot) in
print("snapshot value \(snapshot.value!)")
for rest in snapshot.children.allObjects as! [DataSnapshot] {
guard let restDict = rest.value as? [String:Any] else { continue }
let firstName = restDict["contact_firstName"] as? String ?? ""
cell.UI_fullName.text = "\(firstName)"
}
})
return cell
}
And I think this is where it gets bad because in the console, whatever I do, I constantly have 2 sets of data displayed so my cells are always filled with the same name as you can see here.
This is also a capture of my console after program execution.
Starting observing
Returning count
Number of contact TV: 0
Number of contact TV: 0
Number of contact TV: 0
Got snapshot
2
Number of contact TV: 2
value of numberOfContact 2
value of numberOfContact 2
snapshot value {
1ArkUu6pMPZPgf3pGFAvvxHiTPFaf5Cl = {
"contact_firstName" = Kate;
"contact_lastName" = Bell;
"contact_phoneNumber" = "(555) 564-8583";
};
s8haDrYYAT9Y12ZnmAfE87pDyjDZGwjx = {
"contact_firstName" = Daniel;
"contact_lastName" = Higgins;
"contact_phoneNumber" = "555-478-7672";
};
}
snapshot value {
1ArkUu6pMPZPgf3pGFAvvxHiTPFaf5Cl = {
"contact_firstName" = Kate;
"contact_lastName" = Bell;
"contact_phoneNumber" = "(555) 564-8583";
};
s8haDrYYAT9Y12ZnmAfE87pDyjDZGwjx = {
"contact_firstName" = Daniel;
"contact_lastName" = Higgins;
"contact_phoneNumber" = "555-478-7672";
};
}
Thanks in advance for your help:)

There is a an error in your cellForRowAtIndex Path method.
`ref.child("users").child((user?.uid)!).child("emergencyContacts").observeSingleEvent(of: .value, with: { (snapshot: DataSnapshot) in'
The above line returns all the emergency contacts each time the cellForRowAtIndexPath method is called.
In your for-loop you then always loop through and pick the last value.
You would need to extract the value for the particular index path.

I figured it out, I tried to have a total different reasoning and it worked. I actually used a handler to manage all this. I post my code here, it could help people in the same trouble as I was couple of hours ago.
var emergencyContactList = [EmergencyContact]()
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
fetchUsers()
}
func fetchUsers() {
refHandle = ref.child("users").child((user?.uid)!).child("emergencyContacts").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String:AnyObject] {
print(dictionary)
let emergencyContact = EmergencyContact()
emergencyContact.setValuesForKeys(dictionary)
self.emergencyContactList.append(emergencyContact)
DispatchQueue.main.async {
self.UI_contactsTableView.reloadData()
}
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return emergencyContactList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contact-cell") as! EmergencyContactCell
let _firstName = emergencyContactList[indexPath.row].contact_firstName ?? ""
let _lastName = emergencyContactList[indexPath.row].contact_lastName ?? ""
let _phoneNumber = emergencyContactList[indexPath.row].contact_phoneNumber ?? ""
cell.UI_fullName.text = "\(_firstName) \(_lastName)"
cell.UI_phoneNumber.text = "\(_phoneNumber)"
return cell
}
And I have a swift file apart EmergencyContact.swift
import Foundation
class EmergencyContact: NSObject {
#objc var contact_firstName:String?
#objc var contact_lastName:String?
#objc var contact_phoneNumber:String?
}
Now it works like a charm!
Thanks TheAppMentor for having taken the time to answer me :)

Related

Firebase observe new added data even when the function isn't called

I have a function that observes data from my Firebase database. This data will be inserted in an array, so it can be send to my tableviewcell Viewcontroller. All the data will be put correct in the tabelviewcell, but I have a problem when I update my Firebase database. Everytime I change a value in the database it will immediately update my tableView even when my function is not called. I am not sure what I am doing wrong and how to prevent this.
This is my function observe:
Database.database().reference().child("posts").child("\(postId)").child("comments").observe(.value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
if let postDict = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Comment.transformComment(dict: postDict)
self.comments.insert(post, at: 0)
self.tableView.reloadData()
}
}
}
})
Array:
var comments: [Comment] = []
extension Comment {
static func transformComment(dict: [String: Any]) -> Comment {
let comment = Comment()
comment.commentText = dict["commentText"] as? String
comment.uid = dict["uid"] as? String
comment.timestamp = dict["timestamp"] as? Int
comment.likeCount = dict["likeCount"] as? Int
comment.childByAutoId = dict["childByAutoId"] as? String
comment.id = dict["postId"] as? String
return comment
}
}
Tablevieww Functions:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if comments.count == 0 {
self.tableView.setEmptyMessage("No comments yet!")
} else {
self.tableView.restore()
}
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Comment", for: indexPath) as! CommentTableViewCell
let comment = comments[indexPath.row]
cell.comment = comment
cell.delegate = self
return cell
}
To listen once replace
.child("comments").observe(.value, with: { snapshot in
With
.child("comments").observeSingleEvent(of: .value) { snapshot in
Or
.child("comments").observe(.childChanged) { snapshot in
to listen to added childs

Is it possible to display the 'name' of a firebase node as well as its value in Xcode?

I am trying to show User ID's in my app, however the UID's are very random and next to each one is a name, is it possible to show both the name of the nodes as well as its value?
My firebase database
I want to display the Name "Andy" as well as the UID "uLUnOelxABYl3lCtLz2Of5Yfnvc2"
I have set it up so that the App receives the values from my Firebase.
Here is my code:
class TestViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return list.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = list[indexPath.row]
return cell!
}
#IBOutlet weak var tableView: UITableView!
var ref: DatabaseReference!
var list = [String] ()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
if FirebaseApp.app() == nil {
FirebaseApp.configure() }
ref = Database.database().reference()
ref?.child("users").observe(.childAdded, with: { (snapshot) in
let list1 = snapshot.value as? String
if let actualList = list1 {
self.list.append(actualList)
self.tableView.reloadData()
}
}
)
}
}
Here is what I get back. However I want to show is a list of the names with these values next to it, like this (photoshop)
results
PHOTOSHOP RESUTLS
Regarding to documentation FIRDataSnapshot has property key, so I think you can use it for matching with values, e.g:
/// e.g. use dictionary instead of Array
//var list = [String] ()
var list = [AnyHashable: String]()
...
ref?.child("users").observe(.childAdded, with: { (snapshot) in
let key = snapshot.key as? Hashable
let value = snapshot.value as? String
if let actualValue = value, let actualKey = key {
self.list[key] = actualValue
self.tableView.reloadData()
}
}

Retrieve child key one by one and display in label swift

I'm trying to retrieve the child in Firebase and print them EACH in a label but when I'm retrieving the key its printing all at the same time. I'm still learning on the go so here's what i tried:
func retrieve(){
let userId = Auth.auth().currentUser?.uid
let intakeRef = Database.database().reference().child("Users").child(userId!).child("Intake")
intakeRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let intake = snapshot.value{
self.ref.child("Attendance").child("Checkin").child(intake as! String).child(userId!).child("BM050-3-3-IMNPD").observe(.value, with: { (snapshot) in
for child in snapshot.children{
let key = (child as AnyObject).key as String
print(key)
label.text = key
}
})
}
}) { (Error) in
print("unable to retrieve name from nameRef")
}
}
The code I tried like i said is printing all at the same time. But i would like to print each one in a separate label. Any help would really be appreciated!
You'll of course need to create a tableViewController and link it up to a TableView and assign your cell its own identifier, but this is a good outline of what you'd need to do in your own tableViewController:
class tableViewController: UITableViewController {
var dataArray = [String]()
var ref = Database.database().reference() //You didn't include your delcaration of ref in your code, so don't use this, just use the one you were already using.
override func viewDidLoad() {
super.viewDidLoad()
retrieve()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let data = dataArray[indexPath.row]
cell.textLabel?.text = data
return cell
}
func retrieve(){
let userId = Auth.auth().currentUser?.uid
let intakeRef = Database.database().reference().child("Users").child(userId!).child("Intake")
intakeRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let intake = snapshot.value{
self.ref.child("Attendance").child("Checkin").child(intake as! String).child(userId!).child("BM050-3-3-IMNPD").observe(.value, with: { (snapshot) in
for child in snapshot.children{
let key = (child as AnyObject).key as String
self.dataArray.append(key)
}
})
self.tableView.reloadData()
}
}) { (Error) in
print("unable to retrieve name from nameRef")
}
}
}
Again you'll need to create your own UItableView, link it up to a UITableViewController, assign your own cell reuse identifier and make sure that cellForRowAt has the correct reuse identifier in it, and you'll need to change the ref variable in my provided code to the ref that you need to use.

Firebasedata not populating tableView

I'm trying to read data from Firebase, and write it in a tableView but the data is not populating the tableView
When I print the data inside the closure where I read the data, it prints correctly, but outside the closure it prints blank values. It also prints correctly inside viewDidAppear
import UIKit
import Firebase
class UserProfileTableViewController: UIViewController, UITabBarDelegate, UITableViewDataSource {
private var gotName: String = ""
private var gotAdress: String = ""
private var gotPhone: String = ""
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.separatorColor = UIColor.gray
//Get userinfo from database
let uid = Auth.auth().currentUser!.uid
let userInfoRef = Database.database().reference().child("userprofiles/\(uid)")
userInfoRef.observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as? NSDictionary
let name = value?["Name"] as? String ?? ""
let address = value?["Address"] as? String ?? ""
let phone = value?["Phone"] as? String ?? ""
self.gotName = name
self.gotAdress = address
self.gotPhone = phone
print("Print inside closure in viewDidLoad\(self.gotName, self.gotAdress, self.gotPhone)") //This prints the correct data
// ...
}) { (error) in
print(error.localizedDescription)
}
let testRef = Database.database().reference().child("Test")
testRef.setValue(gotName) // Sets value to ""
print("Print inside outside in viewDidLoad\(self.gotName, self.gotAdress, self.gotPhone)") //This prints blank values
}
override func viewDidAppear(_ animated: Bool) {
print("Print in viewDidAppear closure\(self.gotName, self.gotAdress, self.gotPhone)") //This prints the correct data
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserProfileCell") as! UserProfileCell
cell.userProfileLabel.text = gotName
return cell
}
The print statement outside the closure where I read data in viewDidLoad is the first to be printed in the console if that matters?
Getting data from Firebase or from any server service is done in asynchronous way. That's why when you try to print variables outside closures it doesn't print anything. Try calling tableView.reloadData() inside closure and it will show your desired data.

Swift - How can I group value array (from dictonary) to multiple section

I am a beginner in Swift. How can I group array list from dictionary? I tried, but it show all list into one section. I can't group, list, sort and show list by the same group.
Image 1
But I can do like this,
Image 2
Here's the code for Todolist array
import Foundation
import Firebase
import FirebaseDatabase
struct TodoList {
var title:String!
var content:String!
var username:String!
var dateLabel:String!
var ref : FIRDatabaseReference?
var key: String!
var picNoteStringUrl : String!
var userImageViewStringUrl : String!
var postId: String!
init(title:String,content:String,username:String,picNoteStringUrl : String,userImageViewStringUrl : String,postId: String,dateLabel:String,key:String="") {
self.title=title
self.content=content
self.username = username
self.dateLabel = dateLabel
self.key=key
self.userImageViewStringUrl = userImageViewStringUrl
self.picNoteStringUrl = picNoteStringUrl
self.postId = postId
self.ref=FIRDatabase.database().reference()
}
init(snapshot:FIRDataSnapshot) {
let value = snapshot.value as? [String: AnyObject]
title = value?["title"] as! String
content = value?["content"] as! String
username = value?["username"] as! String
postId = value?["postId"] as! String
picNoteStringUrl = value?["picNoteStringUrl"] as! String
userImageViewStringUrl = value?["userImageViewStringUrl"] as! String
dateLabel = value?["dateLabel"] as! String
key = snapshot.key
ref = snapshot.ref
}
func toAnyObject() -> [String: AnyObject] {
return ["title": title as AnyObject, "content": content as AnyObject,"username": username as AnyObject,"picNoteStringUrl":picNoteStringUrl as AnyObject,"userImageViewStringUrl": userImageViewStringUrl as AnyObject,"postId":postId as AnyObject,"dateLabel" : dateLabel as AnyObject]
}
}
And here's my code for TableViewController
class TodoListTableViewController: UITableViewController{
var storageRef: FIRStorageReference!
var databaseRef : FIRDatabaseReference!
var todoArray:[TodoList] = []
override func viewDidLoad() {
super.viewDidLoad()
if FIRAuth.auth()?.currentUser==nil{
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Home")
self.present(vc,animated: true,completion: nil)
}
else{
let uid = FIRAuth.auth()?.currentUser?.uid
let databaseRef = FIRDatabase.database().reference().child("allTasks").child(uid!)
databaseRef.observe(.value, with: { (snapshot) in
var newItems = [TodoList]()
for item in snapshot.children {
let newTodo = TodoList(snapshot: item as! FIRDataSnapshot)
let letter = newTodo.dateLabel
newItems.insert(newTodo, at: 0)
}
self.todoArray = newItems
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}) { (error) in
print(error.localizedDescription)
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return todoArray.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let todoLine = todoArray[section]
return todoArray.count
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
let todoLine = todoArray[section]
return todoLine.dateLabel
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TodoTableViewCell
cell.todoItemName.text = self.todoArray[indexPath.row].title
cell.todoDescription.text = self.todoArray[indexPath.row].content
cell.usernameLabel.text = self.todoArray[indexPath.row].username
let picNoteStringUrl = self.todoArray[indexPath.row].picNoteStringUrl
let userImageViewStringUrl = self.todoArray[indexPath.row].userImageViewStringUrl
FIRStorage.storage().reference(forURL: picNoteStringUrl!).data(withMaxSize: 10 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async(execute: {
if let picNoteStringUrl = UIImage(data:data!) {
cell.picNote.image = picNoteStringUrl
print("testpass",picNoteStringUrl)
}
})
}else {
print(error!.localizedDescription,"555")
}
})
FIRStorage.storage().reference(forURL: userImageViewStringUrl!).data(withMaxSize: 10 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async(execute: {
if let userImageViewStringUrl = UIImage(data:data!) {
cell.userImageView.image = userImageViewStringUrl
print("testpass",userImageViewStringUrl)
}
})
}else {
print(error!.localizedDescription,"555")
}
})
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath:IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .default, title: "\u{267A}\n Delete") { action, index in
print("more button tapped")
let ref = self.todoArray[indexPath.row].ref
ref?.removeValue()
self.todoArray.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
delete.backgroundColor = UIColor.red
let check = UITableViewRowAction(style: .default, title: "\u{2611}\n check") { action, index in
print("edit button tapped")
}
check.backgroundColor = UIColor.orange
return [check, delete]
}
}
}
}
You have to organize your data by section first. I don't see that happening since you simply add all received items into one array.
Based on the screenshot you have provided and the project, it looks as if you are trying to display todo items by date where each section is for a different date. And as far as I can tell, your date value is in the dateLabel property.
If all of the above is correct, then you would need to convert the dateLabel property, which is a String, to an actual Date value so that you can work with the individual dates. Or, depending on how the date string is set up, you might be able to do the same thing by getting just the date component of the string. For example, if your date strings are like "2017-03-31 10:55am" or something, just getting the "2017-03-31" part should allow you to organize the todo items so that all items for the same date can be easily identified.
Once you do that, you have to set up some sort of a structure - if you go with date strings, then a dictionary might work - where you can identify all todo items for a given date. For example, if you have just the date extracted as a string (like "2017-03-31") then you could set up something like this:
var dates = [String]()
var todoItems = [String:[TodoList]]()
The above means that for one string value (which would be a date), you'd have an array of TodoList items. The dates array would be just a convenience so that you can sort the date strings the way you want.
Once you have that, you can modify your table delegate methods to get the count of items in dates to get the sections and the relevant TodoList for each row. Like this:
override func numberOfSections(in tableView: UITableView) -> Int {
return dates.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let date = dates[section]
let array = todoItems[date]
return array.count
}
Hopefully, the above makes sense :)