Why .childAdded is not called when new data is added? Firebase - swift

I am trying to read data from media when data is updated on /media node, but .observe(.childAdded is not called.
For example, I update data at /media/-LKN1j_FLQuOvnhEFfao/caption , but I never receive the event in observeNewMedia .
I can read the data with no problem the first time when ViewDidLoad completes.
The first step is to download the user data, second is to get the locality from currentUser and the last step is to attach a listener .childAdded on media.
I suspect that the event is not triggered because fetchMedia is called inside DDatabaseRReference.users(uid: uid).reference().observe(.value
media
-LKNRdP4ZsE3YrgaLB30
caption: "santa"
mediaUID: "-LKNRdP4ZsE3YrgaLB30"
locality: "barking"
users
Q6Dm3IMLNLgBH3ny3rv2CMYf47p1
media
-LKNReJCxgwtGRU6iJmV: "-LKNRdP4ZsE3YrgaLB30"
email: "john#gmail.com"
locality: "barking"
//enables the programmer to create references to different childs in Firebase
enum DDatabaseRReference {
case root
case users(uid:String)
case media //to store photos
func reference() -> DatabaseReference {
return rootRef.child(path)
}
//return root reference to our database
private var rootRef: DatabaseReference {
return Database.database().reference()
}
private var path: String {
switch self { //self is the enum DDatabaseReference
case .root:
return ""
case .users(let uid):
return "users/\(uid)"
case .media:
return "media"
}
}
}//end of enum DatabaseReference
class NewsfeedTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
//observe ~/users/uid
DDatabaseRReference.users(uid: uid).reference().observe(.value, with: { (snapshot) in
DispatchQueue.main.async {
if let userDict = snapshot.value as? [String : Any] {
self.currentUser = UserModel(dictionary: userDict)
self.fetchMedia()
self.tableView.reloadData()
}
}
})
}
func fetchMedia() {
Media.observeNewMedia((currentUser?.locality)!) { (newMedia) in
//check if newly downloaded media is already in media array
if !self.media.contains(newMedia) {
self.media.insert(newMedia, at: 0)
self.tableView.reloadData()
}else {
//remove old media and add the newly updated one
guard let index = self.media.index(of: newMedia) else {return}
self.media.remove(at: index)
self.media.insert(newMedia, at: 0)
self.tableView.reloadData()
}
}
}
}//end of NewsfeedTableViewController
class Media {
class func observeNewMedia(_ userLocality: String, _ completion: #escaping (Media) -> Void) {
DDatabaseRReference.media.reference().queryOrdered(byChild: "locality").queryEqual(toValue: userLocality).observe(.childAdded, with: { snapshot in
guard snapshot.exists() else {
print("no snap ")
return}
print("snap is \(snapshot)")
let media = Media(dictionary: snapshot.value as! [String : Any])
completion(media)
})
}
} //end of class Media

Let's first update the structure so make it more queryable
assume a users node
users
-Q6Dm3IMLNLgBH3ny3rv2CMYf47p1 //this is each users uid
email: "john#gmail.com"
locality: "barking"
and a media node that contains media for all users
media
-abcdefg12345 //node created with childByAutoId
caption: "santa"
for_uid: -Q6Dm3IMLNLgBH3ny3rv2CMYf47p1 //matches the uid in the /users node
Then our main viewController which contains a reference to Firebase and logs the user in
class ViewController: UIViewController {
var ref: DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
self.ref = Database.database().reference()
//log user in which will populate the Auth.auth.currentUser variable
}
.
.
.
We need an object to store the media in and then an array to hold those objects
class MediaClass {
var key = ""
var caption = ""
init(k: String, c: String) {
self.key = k
self.caption = c
}
}
var mediaArray = [MediaClass]()
then set up the observers which will add, update or remove from the array when media for this user is added, changed or removed.
let thisUid = Auth.auth().currentUser?.uid
let mediaRef = self.ref.child("media")
let queryRef = mediaRef.queryOrdered(byChild: "for_uid").queryEqual(toValue: thisUid)
queryRef.observe(.childAdded, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let key = snapshot.key
let caption = dict["caption"] as! String
let m = MediaClass.init(k: key, c: caption)
self.mediaArray.append(m)
self.tableView.reloadData()
})
queryRef.observe(.childChanged, with: { snapshot in
let dict = snapshot.value as! [String: Any]
let key = snapshot.key
let caption = dict["caption"] as! String
let index = self.mediaArray.index { $0.key == key } //locate this object in the array
self.mediaArray[index!].caption = caption //and update it's caption
self.tableView.reloadData()
})
//leaving this an an exercise
queryRef.observe(.childRemoved....
Note we added .childAdded, .childChanged and .childRemoved events to the media node via a query so the only events the app will receive are the ones that pertain to this user.
Also note there's no error checking so that needs to be added.

Related

Firebase data to tableview, crash after appending data

I am trying to download data from Firebase using
firebaseDB.observe(DataEventType.value, with: { (snapshot) in })
which works well. I am able to receive the following data:
let fetchedObject = items.value as? [String: AnyObject]
let dbUrl = fetchedObject?["url"]
let dbTime = fetchedObject?["time"]
let dbStatus = fetchedObject?["status"]
Using print("\(dbUrl!), \(dbTime!), \(dbStatus!)"), I see the following output in the console:
https://google.de, 2019-04-26 07:44:54 +0000, new
However, because I want to show the data in a Tableview, I have created a swift file for a data model, called ItemModel with the following content:
class ItemModel {
var url: String?
var time: String?
var status: String?
init(url: String?, time: String?, status: String?){
self.url = url
self.time = time
self.status = status
}
}
When trying to populate the url, time and status using
let item = ItemModel(url: dbUrl as! String?, time: dbTime as! String?, status: dbStatus as! String?)
appending it with self.itemList.append(item), and reloading the table with self.tableViewOutlet.reloadData(), the app crashes.
If I comment out self.itemList.append(item), it does not crash.
What am I doing wrong? I am clearly missing something viable, but I just can't figure out what. I am really wondering why appending the data does not work... Thanks a lot for any help!
EDIT:
Complete fetching method:
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var itemList = [ItemModel]()
override func viewDidLoad() {
super.viewDidLoad()
[...]
// Authenticate user
Auth.auth().signInAnonymously() {(authResult, error) in
if error == nil {
self.user = authResult!.user // Authenticate User
self.uid = self.user.uid // Users unique and anonymous identifier by Firebase
print(">> LOG: Auth success!")
// Download data from Firebase
var itemsDB: DatabaseReference!
itemsDB = Database.database().reference().child("somechild").child("someotherchild")
itemsDB.observe(DataEventType.value, with: { (snapshot) in
// Check if there is any relevant data
if snapshot.childrenCount > 0 {
// Initially clear the list
self.itemList.removeAll()
// Interate through all the values
for items in snapshot.children.allObjects as! [DataSnapshot] {
// Get the values from Firebase
let fetchedObject = items.value as? [String: AnyObject]
let dbUrl = fetchedObject?["url"]
let dbTime = fetchedObject?["time"]
let dbStatus = fetchedObject?["status"]
let item = ItemModel(url: dbUrl as? String, time: dbTime as? String, status: dbStatus as? String)
print("\(item.url!), \(item.time!), \(item.status!)")
self.itemList.append(item)
}
// Reload the Tableview
self.tableViewOutlet.reloadData()
}
})
} else {
print(">> LOG: Error when trying to authenticate: \(error!)")
}
}
}
}

How to Update CollectionView Model with Firebase?

I am trying to intitialize my Collection View Model which holds a list of [Users] with a firebase snap that holds data.
Here is the Collection View Model code:
typealias JSON = [String:Any]
class HomeDataSource: Datasource {
var users: [User]
init?(with snapshot:DataSnapshot) {
var users = [User]()
guard let snapDict = snapshot.value as? [String: [String:Any]] else {return nil}
for snap in snapDict {
guard let user = User(dict: snap.value) else {continue}
users.append(user)
}
self.users = users
}
}
User Model:
struct User {
let name: String
let username: String
init?(dict: JSON) {
guard let name = dict["name"] as? String,
let email = dict["email"] as? String
else {return nil}
self.name = name
self.username = email
}
}
Firebase Snap:
Snap (users) {
8CVeHMNHI6hZAWj1zhGHEjPwYYz1 = {
email = "Silviu#isidors.sjsj";
name = Bshdjdj;
};
9CuqgR4Es7TOPPJQEpSnQXlfYnm1 = {
email = "Test#silviu.com";
name = "Test#silviu.com";
};
DBqGWlpdJKME570euqUz2rqI5Z83 = {
email = "Test#test.test";
name = Test;
};
}
Fetch Function:
func fetchUser() {
let ref = Database.database().reference().child("users")
ref.observe(.childAdded, with: { (snapshot) in
let user = User(dict: snapshot.value as! JSON)
self.users.append(user!)
print(self.users)
let new = HomeDataSource(with: snapshot)
print(new)
DispatchQueue.main.async(execute: {
self.datasource = new
self.collectionView?.reloadData()
})
}, withCancel: nil)
}
Right now, I am getting an array of Users from Firebase, however my collection view won't update.
My question is how should I update my Collection View Model and Fetching Function so it can fetch data from Firebase and Populate the Collection View Correctly?
So the problem was the node I was targeting. Removed "child("users")" form fetching function and targeted the whole users node with uids. Then I was looping the snapshot.value while I casted it in [String:[String:Any]], because each snapshot.valuelooked like this (key: "vijwUkzAlbgcqjAammfy0JuVMB33", value: ["name": Silviu, "email": Office#isidors.com]) Finally, I updated the HomeDataShource Class like this:
class HomeDataSource: Datasource {
var users: [User]
init(with snapshot:DataSnapshot) {
var users = [User]()
let snapDict = snapshot.value as? [String:[String:Any]]
for snap in snapDict! {
guard let user = User(snap: snap.value) else {continue}
users.append(user)
}
self.users = users
}

How can I pick three random elements out of a dictionary in Swift 4.1

I am having a problem picking three random elements out of a dictionary.
My dictionary code:
query.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let childSnap = child as! DataSnapshot
var dict = childSnap.value as! [String: Any]
}
})
You can use an array if keys are integers.
if you want to use a dictionary only then below mentioned code might be helpful for you
var namesOfPeople = [Int: String]()
namesOfPeople[1] = "jacob"
namesOfPeople[2] = "peter"
namesOfPeople[3] = "sam"
func makeList(n: Int) -> [Int] {
print(namesOfPeopleCount)
return (0..<n).map { _ in namesOfPeople.keys.randomElement()! }
}
let randomKeys = makeList(3)
You can try this for older version Of Swift where randomElement() is not available
let namesOfPeopleCount = namesOfPeople.count
func makeList(n: Int) -> [Int] {
return (0..<n).map{ _ in Int(arc4random_uniform(namesOfPeopleCount)
}
#Satish answer is fine but here's one which is a bit more complete and selects a random user from a list of users loaded from Firebase ensuring a user is only selected once.
We have have an app with two buttons
populateArray
selectRandomUser
and we have a UserClass to store our user data for each user.
class UserClass {
var uid = ""
var name = ""
init(withSnapshot: DataSnapshot) {
let dict = withSnapshot.value as! [String: Any]
self.uid = withSnapshot.key
self.name = dict["Name"] as! String
}
}
and an array to store the users in
var userArray = [UserClass]()
When the populateArray button is clicked this code runs
func populateArray() {
let usersRef = self.ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let user = UserClass(withSnapshot: snap)
self.userArray.append(user)
}
print("array populated")
})
}
and then to select a random user use this code.
func selectRandomUser() {
if let someUser = userArray.randomElement() {
print("your random user: \(someUser.name)")
let uid = someUser.uid
if let index = userArray.index(where: { $0.uid == uid } ) {
userArray.remove(at: index)
}
} else {
print("no users remain")
}
}
This code ensures the same user is not selected twice. Note that this is destructive to the array containing the users so if that's unwanted, make a copy of the array after it's populated and work with that.

Swift, FIrebase - Can't remove observers by removeAllObservers

I've been trying to remove observers for a while but somehow I have never succeeded. I've checked other posts here but I can’t find the reason why.
Here are my codes:
var ref: DatabaseReference!
let timeStamp: Double = NSDate().timeIntervalSince1970
override func viewDidLoad() {
super.viewDidLoad()
self.ref = Database.database().reference()
guard let userId = Auth.auth().currentUser?.uid else { return }
self.ref.child("users").child(userId).child("contactList").observe(.value, with: { (snapshot) in
guard let children = snapshot.value as? [String: Any] else { return }
for child in children {
guard let dictionary = child.value as? [String: Any] else { return }
guard let timeStamp = dictionary["timeStamp"] as? String else { return }
guard let timeStampDouble = Double(timeStamp) else { return }
if timeStampDouble > self.timeStamp {
self.navigationController?.pushViewController(HomeController(), animated: true)
}
}
}) { (err) in
print("Failed to fetch user: ", err)
}
}
deinit {
self.ref.child("users").removeAllObservers()
}
I would appreciate any advise!
Calling removeAllObservers on a node, removes all observers from that node only. It doesn't remove observers from child nodes.
So your code:
self.ref.child("users").removeAllObservers()
This only removes the observers from users. It does not remove the observers from users/$userId/contactList. To remove the latter, you will have to call removeAllObservers on that specific node, which means you'll need to track what nodes you have attached observers to.

How can I retrieve data from firebase (Swift)

I am beginner and I would like to use swift as my programming language.
user can add register users and transfer money to each other, therefore when user type the photo number and email, the app can check if the typed number is registered in the firebase system
Any simple way to search if the user exist, thanks a lot
Here's the structure of the database
var ref: DatabaseReference!
var tref: DatabaseReference!
var handle : DatabaseHandle!
var usersArray = [NSDictionary?]()
var filteredUsers = [NSDictionary?]()
var user : NSDictionary?
override func viewDidLoad() {
super.viewDidLoad()
self.picker.isHidden = true
tref = Database.database().reference()
ref = Database.database().reference()
self.handle = self.ref?.child("users").child((Auth.auth().currentUser?.uid)!).child("contact").observe(.childAdded, with: { (snapshot) in
if let item = snapshot.value as? String {
self.pickerdata.append(item)
self.picker.reloadAllComponents()
}
})
self.picker.delegate = self
self.picker.dataSource = self
tref.child("users").queryOrdered(byChild: "phone").observe(.childAdded, with: {(snapshot) in
self.usersArray.append(snapshot.value as? NSDictionary)
})
print(usersArray.count)
}
#IBAction func ContactChange(_ sender: UITextField) {
filteredContent(searchText: contactText.text!)
print(filteredUsers.count)
print(usersArray.count)
print("ARRAY")
}
func filteredContent(searchText: String){
self.filteredUsers = self.usersArray.filter{ user in
let username = user!["phone"] as? String
return (username?.lowercased().contains(searchText.lowercased()))!
}
}
func findUsers(text: String){
self.handle = ref.child("users").queryOrdered(byChild: "phone").queryStarting(atValue: contactText.text!).queryEnding(atValue: contactText.text!+"\u{f8ff}").observe(.value, with: { snapshot in
if let item = snapshot.value as? String {
self.contact.append(item)
} else{
print("error")
}
})
}
The code above doesn't work much. Thanks so much for helping
The question is a little unclear but I think what the OP is asking is:
how to see if a user exists by their phone number?
If that's what's being asked, a node can be retrieved by query'ing for a child of that node. For example, let's create a query on the users node to find the child that contains the name: Kam querying by phone
let usersRef = firebaseRef.child("users")
let query = usersRef.queryOrdered(byChild: "phone").queryEqual(toValue: "2330504")
query.observeSingleEvent(of: .value) { (snapshot) in
if snapshot.exists() {
//need to iterate in case we find more than one match
for child in snapshot.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let name = dict["name"] as! String
print("found \(name)")
}
} else {
print("not found")
}
}
When this code is run it would print
found Kam
assuming the phone number is 2330504