.Childadded Observer Doesn't Get Called If The Function Is Not Called Explicitly. Swift 4

.childAdded observer doesn't get called If the function is not called explicitly. Swift 4

Instead of a lengthy discussion in comments let's try to answer this with a couple of links and a code example.

First though, you should only synchronize data when a view is visible and the viewWillAppear method is called each time the view becomes visible, so that's a good place to add observers. It's also good practice to remove observers when you don't need them (saves bandwidth) and that can be done using a firebase handle in the viewDidDisappear. Here's a slightly dated article but a great read

Best Practices for UIViewController and Firebase

and for a great example, see the answer to this question

Firebase: when to call removeObserverWithHandle in swift

To address the rest of the question (note I am keeping this short so I didn't include using handles)

I have a class to store alerts

class AlertClass {
var node_key = ""
var msg = ""

init(aKey: String, aMsg: String) {
self.node_key = aKey
self.msg = aMsg
}
}

and then a class var array to store all of the alerts

var alertArray = [AlertClass]()

and then we add our observers from the viewWillAppear function

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.addObservers()
}

which adds three observers to the referenced node; .childAdded, .childChanged, and .childRemoved. Keep in mind that .childAdded will iterate over the nodes in the ref node and populate our dataSource any time viewWillAppear is called, so we need to 'reset' the array so we don't accidentally load data on top of the existing data. Your use case may differ so code accordingly.

Here's the code to add the observers and prints the array any time there's a change.

func addObservers() {
let ref = self.ref.child("Continent").child("Europe").child("Country").child("Italy").child("Region").child("Emilia-Romagna").child("City").child("Bologna").child("Community").child("Alert Notifications")
self.alertArray = []
ref.observe(.childAdded, with: { (snapshot) in
let key = snapshot.key
let msg = snapshot.childSnapshot(forPath: "msg").value as! String
let aAlert = AlertClass(aKey: key, aMsg: msg)
self.alertArray.append(aAlert) //append the new alert
self.showAlertArray() //this is called for every child
})

ref.observe(.childChanged, with: { (snapshot) in
let key = snapshot.key
let msg = snapshot.childSnapshot(forPath: "msg").value as! String
if let foundAlert = self.alertArray.first(where: { $0.node_key == key } ) {
foundAlert.msg = msg //update the alert msg
self.showAlertArray()
}
})

ref.observe(.childRemoved, with: { (snapshot) in
let key = snapshot.key
self.alertArray.removeAll(where: { $0.node_key == key }) //remove the alert
self.showAlertArray()
})
}

func showAlertArray() {
for alert in self.alertArray {
print(alert.node_key, alert.msg)
}
}

and as a side note...

If you're populating a tableView dataSource via the childAdded you may be wondering how to do that without calling tableView.reloadData repeatedly, which may cause flicker. There's a techniqure for doing that by leveraging the fact that .value events are called after .childAdded. See my answer to this question for an example.

Some questions about keepSynced(true) on a limited query reference

As its name implies keepSynced(true) keeps whatever query or reference you call it on synchronized in the local cache. It quite literally just attaches an empty observer to that query/reference. So in your Database().database.reference.child("posts").child(uid).queryLimited(toLast: 25) it will sync the last 25 child nodes, and keep synchronizing those (removing previous ones as new ones are added).

Firebase Realtime Database caching mechanism works most reliably if you repeatedly listen for the exact same data. Specifically, a .value listener attached to Database().database.reference.child("posts").child(uid) may not see data that was cached through Database().database.reference.child("posts").child(uid).queryLimited(toLast: 25). This is because the Firebase client guarantees to never fire events for partial updates, and in this example it can't guarantee that it has all data from the first reference.

For your questions:

  1. See above...

  2. It's most common to add them in viewWillAppear.

  3. I'm not sure why you'd want to call keepSynced false, so can't recommend anything there.

  4. Not sure if this is what you mean, but keepSynced(true) is not persisted between runs of the app. So you have to call keepSynced(true) each time your app/view starts.

  5. See above...

In general you seem to try and work around the way the API works, by calling the API in different ways. I typically don't see great results from that. If you want your app to behave differently than the API does, consider creating a custom wrapper, and caching the data there.

Accessing Nested Children in Firebase Database Swift 3

The code for doing specifically what you are asking is

let customerRef = self.ref.child("customer")
customerRef.observe(.childAdded, with: { snapshot in
let subscriptionSnap = snapshot.childSnapshot(forPath: "subscription")
for child in subscriptionSnap.children {
let snap = child as! DataSnapshot
let dict = snap.value as! [String: Any]
let subNo = dict["sub_no"] as! String
print(subNo)
}
})

and the output is

a123
a456
a789

*note that I am reading the sub_no as a STRING which is why I added 'a' in front. If they are actually integers change the line to

let subNo = dict["sub_no"] as! Integer

*note2 this will leave a .childAdded observer to the main node in question so any further children that are added will fire the code in the closure.

Edit:

If you want to just retrieve all of the data at one time and not leave a childAdded observer then this will do it:

let customerRef = self.ref.child("customer")
customerRef.observeSingleEvent(of: .value, with: { snapshot in
for customerChild in snapshot.children {
let childSnap = customerChild as! DataSnapshot
let subscriptionSnap = childSnap.childSnapshot(forPath: "subscription")
for subscriptionChild in subscriptionSnap.children {
let snap = subscriptionChild as! DataSnapshot
let dict = snap.value as! [String: Any]
let subNo = dict["sub_no"] as! String
print(subNo)
}
}
})

and the output is

a123
a456
a789

Cloud Functions for Firebase database trigger doesn't fire on parent removal

Yea looks like a duplicate of Cloud Functions for Firebase onWrite trigger not called when parent is deleted

I hate it when I search and still can't find it when someone else answers the same question.

If any firebaser reads this, it would still be helpful see a tag for "Cloud Functions for Firebase"

Access Multiple Nodes in Firebase Database

As it sits, I am really not seeing anything super wrong with the code but there are few things that could be changed to make it more streamlined.

I would first change the Consideration Info class to make it more self contained. Add a convenience initializer to handle a firebase snapshot directly with some error checking.

class ConsiderationInfo: NSObject {
var companyName = ""

convenience init(withSnapshot: DataSnapshot) {
self.init()
self.companyName = withSnapshot.childSnapshot(forPath: "Company Name").value as? String ?? "No Company Name"
}
}

I would also suggest removing the .childAdded and .observe events unless you specifically want to be notified of future changes. Use .value and .observeSingleEvent instead. Keeping in mind that .childAdded iterates over each node in your database one at a time - .value reads them in all at the same time. If there is a limited amount of data, .value works well.

    func updateConsiderationsArray() {
let fbRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
let ref = fbRef.child("user_considerations").child(uid)
ref.observeSingleEvent(of: .value, with: { snapshot in
let allUidsSnap = snapshot.children.allObjects as! [DataSnapshot]
for uidSnap in allUidsSnap {
let considerationId = uidSnap.key
let considerationReference = fbRef.child("Considerations").child(considerationId)
considerationReference.observeSingleEvent(of: .value, with: { snapshot in
let considerationInfo = ConsiderationInfo(withSnapshot: snapshot)
self.considerationArray.append(considerationInfo)
// update your collectionView
})
}
})
}

What I am doing in the above is reading in the single node from user_considerations, which looks like this according to your quuestion

user_considerations
some_user_uid
a_user_uid
a_user_uid

and then mapping each child user to an array to maintain order

let allUidsSnap = snapshot.children.allObjects as! [DataSnapshot]

and then iterating over each, getting the uid (the key) of each node and getting that nodes data from the Considerations node.

Firebase Object Ownership with Event Observation

What you are missing is that you are assuming that security rules are queries and that is not true.

Check the
Rules are Not Filters section in the link.

Security rules only validate if you can read or write a specific path of your firebase database.

If you want to only receive changes of a specific user you should use firebase queries.

For example if you want to get all the tasks of a specific user, you should do:

let reference = Firebase(url: "https://{My-Firebase-Base-Reference}/tasks")
reference.queryOrderedByChild("creatorId").queryEqualTo(YOUR_CURRENT_USER_UID).observeEventType(.ChildChanged,
withBlock: { (snapshot: FDataSnapshot!) -> Void in
// Success
}) { (error: NSError!) in
// Error: Get Permissions Denied Here.
}

This way you could get all the events only related to your user, and protect the information by applying the security rules.

also if you want to allow only the creator to write their own tasks you should also consider the case where you create the task and write something like this:

  "tasks": {
//You need to include the $task_id otherwise the rule will seek the creatorId child inside task and not inside your auto-generated task
"$task_id": {
".read": "auth.uid === data.child('creatorId').val()",
//this is to validate that if you are creating a task it must have your own uid in the creatorId field and that you can only edit tasks that are yours...
".write":"(newData.child('creatorId').val() === auth.uid && !data.exists()) || (data.child('creatorId').val() === auth.uid && data.exists())",
//you should add the index on to tell firebase this is a query index so your queries can be efficient even with larger amounts of children
".indexOn":"creatorId",
}
}

(check the syntax but that's the general idea)



Related Topics



Leave a reply



Submit