Search for users that haven't been fetch yet - swift

I have a quick question, I build a block of code that fetches all the users in the database for searching purposes. Preparing for thousands of users in the future, I programmed the method in a pagination way fetching a certain amount of users at a time. That is where the problem lies when I search for a user if the user hasn't been retrieve from the database yet through scrolling I can't search there profile. Does anyone have a suggestion on how I can tackle this?
Here is the code I use to fetch the users:
//create a method that will fetch a certain mount of users
func fetchUsers() {
if userCurrentKey == nil {
USER_REF.queryLimited(toLast: 21).observeSingleEvent(of: .value) { (snapshot) in
self.collectionView?.refreshControl?.endRefreshing()
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard let allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let uid = snapshot.key
Database.fetchUser(with: uid, completion: { (user) in
self.users.append(user)
self.collectionView?.reloadData()
})
})
self.userCurrentKey = first.key
}
} else {
USER_REF.queryOrderedByKey().queryEnding(atValue: userCurrentKey).queryLimited(toLast: 22).observeSingleEvent(of: .value, with: { (snapshot) in
guard let first = snapshot.children.allObjects.first as? DataSnapshot else { return }
guard let allObjects = snapshot.children.allObjects as? [DataSnapshot] else { return }
allObjects.forEach({ (snapshot) in
let uid = snapshot.key
if uid != self.userCurrentKey {
Database.fetchUser(with: uid, completion: { (user) in
self.users.append(user)
self.collectionView?.reloadData()
})
}
})
self.userCurrentKey = first.key
})
}
}
}
Here is the code I used to paginate the users:
//once the users pass a certain amount of cells paginate to fetch the next set of users
override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
if users.count > 20 {
if indexPath.item == users.count - 1 {
print("Fetching...")
fetchUsers()
}
}
}
Lastly here is the code I used to filter through the users:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty{
//if search text is empty fetch the users but display nothing
inSearchMode = false
filteredUsers = users
self.collectionView?.refreshControl = refreshController
} else {
//if search text is not empty search for the users
inSearchMode = true
self.collectionView?.refreshControl = nil
filteredUsers = self.users.filter { (user) -> Bool in
return user.username.lowercased().contains(searchText.lowercased())
}
}
//reload the table view data to update the displayed user
self.collectionView?.reloadData()
}
Thank you in advance!

The up front issue is you should not be in a situation where you have to load all users to search through them.
If you have a lot of data in the users node, loading that much data can overwhelm the device and secondly it's going to get very laggy for the user as the loaded data is iterated over.
Your best move is to denormalize your data and let the server do the heavy lifting by performing those queries and delivering only the data you need. Way faster and much easier to maintain. You can also add additional nodes to get to the data you want.
Looking at the code (I don't know your structure) it appears your goal is to have a searchfield where the users can type a username and the goal is to query for that username and return it lowercased.
A solution is to update your Firebase structure. Suppose it's like this
users
uid_0
userName: "MyCrazyUserNAME"
if you want to search, lowercased, add another node to your structure that's a lowercased version of the name
users
uid_0
userName: "MyCrazyUserNAME"
lowerCased: "mycrazyusername"
Then, perform a partial string query on the lowerCased node as the user types
func searchFor(thisPartialString: String) {
let userRef = self.ref.child("users")
let startString = thePartialString
let endString = thisPartialString + "\\uf8ff"
let query = ref.queryOrdered(byChild: "lowerCased")
.queryStarting(atValue: startString)
.queryEnding(atValue: endString")
query.observe....
}
The "\uf8ff" is a character at a very high code level in Unicode - because of that it encompasses all of the preceeding characters.

Related

How I can add pagination in swift?

I have spend so much time to find a solution with the documentation from Firebase without any success. I using Swift 5.3 and Firestore and have the following code:
func readFlights() {
Spinner.startAnimating()
let myquery = db.collection("flight").limit(to: 25).whereField("Userid", isEqualTo: userID).order(by: "DateDB", descending: true)
.order(by: "Start", descending: true)
myquery.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
self.prefixArray.append(document.get("Prefix") as! String)
self.dateArray.append(document.get("Date") as! String)
self.startArray.append(document.get("Start") as! String)
self.stopArray.append(document.get("Stop") as! String)
self.landingArray.append(document.get("Landing") as! String)
self.takeOffArray.append(document.get("Takeoff") as! String)
self.flightTimeArray.append(document.get("FlightTime") as! String)
self.engineTimeArray.append(document.get("EngineTime") as! String)
self.idArray.append(document.get("id") as! String)
self.destinationArray.append(document.get("Destination") as! String)
self.originArray.append(document.get("Origin") as! String)
self.informationArray.append(document.get("Addinfo") as! String)
self.rulesArray.append(document.get("VFRIFR") as! Int)
self.pilotCopilotArray.append(document.get("PilotoCopiloto") as! Int)
self.engineArray.append(document.get("MnteMlte") as! Int)
self.dayNightArray.append(document.get("DayNight") as! Int)
}
DispatchQueue.main.async{
self.tabelView.reloadData()
self.Spinner.stopAnimating()
}
}
}
working fine but I need to include in this code pagination. That means when I received the first 25 records from Firestore and I slip down in the list with my finger so I want after the latest record he load 25 records more and show them.
I would appreciate your help. Thank you
First, create a document cursor that is an instance property of the view/view controller:
var cursor: DocumentSnapshot?
let pageSize = 25 // for convenience
Second, apply the page size to the query:
let myquery = db.collection("flight").limit(to: pageSize).whereField("Userid", isEqualTo: userID).order(by: "DateDB", descending: true).order(by: "Start", descending: true)
Third, whenever you receive a snapshot from Firestore, update the cursor at some point in the return (ideally, after you've unwrapped the snapshot and before you've parsed the documents):
func getData() {
myquery.getDocuments(completion: { (snapshot, error) in
...
if snapshot.count < pageSize {
/* this return had less than 25 documents, therefore
there are no more possible documents to fetch and
thus there is no cursor */
self.cursor = nil
} else {
/* this return had at least 25 documents, therefore
there may be more documents to fetch which makes
the last document in this snapshot the cursor */
self.cursor = snapshot.documents.last
}
...
})
}
Finally, whenever the user scrolls to the bottom, fetch another page using the cursor:
func continueData() {
guard let cursor = cursor else {
return // no cursor, exit
}
myquery.start(afterDocument: cursor).getDocuments(completion: { (snapshot, error) in
...
// always update the cursor whenever Firestore returns
if snapshot.count < self.pageSize {
self.cursor = nil
} else {
self.cursor = snapshot.documents.last
}
...
})
}
For a fluid user experience, you will need to greatly refine this code, but this is the foundation from which you can paginate Firestore. You can also paginate in Firestore using a document offset (instead of a document cursor) but this is to be avoided (refer to documentation for the reasons).
You can use awesome solution from: https://github.com/pronebird/UIScrollView-InfiniteScroll
For your example:
tableView.addInfiniteScroll { (tableView) -> Void in
readFlights("offset if need")
tableView.finishInfiniteScroll()
}
By using UITableViewDelegate, u can call the function. Each time when you scroll to the bottom, it will check the max of your limit and if the condition is true, then fetch data again.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastItem = self.array.count - 1
if indexPath.row == lastItem {
if limit < max_limit {
limit += 25
//Get data from Server
readFlights(limit:Int)
}
}
}
The max_limit means the total amount of limits, usually, it returned by server in meta

I want my code to run consecutively/synchronously in the background (DispatchQueue)

I want grabAllFollowingPosts() to run only after loadFollowing() has finished running. These are both network calls so I want to run them in the background. Any ideas on why my code isn’t working?
DispatchQueue.global(qos: .userInteractive).sync {
self.loadFollowing()
self.grabAllFollowingPosts()
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
What these 3 functions do is:
grab every user that the current user is following
for each of those users, grab their posts
Hence, loadUsers() must run before grabAllFollowingPosts()
var followingUsers = [String]()
//Function 1: load the poeple you are following into the followingUsers array
func loadFollowing () {
guard let userID = Auth.auth().currentUser?.uid else { return }
let firestoreRef = Firestore.firestore().collection("Following").document(userID).collection("UserFollowing")
firestoreRef.addSnapshotListener { (snapshot, error) in
if error != nil {
//error retrieving documents
print (error!.localizedDescription)
} else {
// document retrival successful
guard let snapshot = snapshot else { return }
for document in snapshot.documents {
let data = document.data()
let userid = data["UserID"] as? String ?? "anonymous"
self.followingUsers.append(userid)
}
}
}
}
//Function 2: for all of the users in the followingUsers array - grab their documents from Firestore
func grabAllFollowingPosts () {
for users in followingUsers {
loadPosts(theUsers: users)
}
}
//Function 3: loads the posts
func loadPosts (theUsers: String) {
let firestoreRef = Firestore.firestore().collection("Posts").whereField("UserID", isEqualTo: theUsers).whereField("Date", isGreaterThanOrEqualTo: Date()).limit(to: 8)
//TODO: add infinate scroll
firestoreRef.addSnapshotListener { (snapshot, error) in
if error != nil {
//error retrieving documents
print (error!.localizedDescription)
} else {
// document retrival successful
guard let snapshot = snapshot else { return }
for document in snapshot.documents {
let data = document.data()
let ageRestriction = data["AgeRestriction"] as? String ?? "Age E"
let category = data["Category"] as? String ?? "Error - No Category"
let date = data["Date"] as? Date ?? Date()
let documentId = data["DocumentID"] as? String ?? "Error - No Document-ID"
let description = data["Description"] as? String ?? "Error - No Description"
let location = data["Location"] as? String ?? "Error - No Location"
let title = data["Title"] as? String ?? "Error - No Title"
let userId = data["UserID"] as? String ?? "Error - No User-ID"
let username = data["Username"] as? String ?? "Anonymous"
let color = data["Color"] as? String ?? "Sale"
let newPost = Post(documentIDText: documentId, usernameText: username, titleText: title, locationText: location, dateText: date, descriptionText: description, ageText: ageRestriction, category: category, uid: userId, color: color)
self.posts.append(newPost)
}
if self.posts.isEmpty {self.goFollowPeopleImage.isHidden = false}
}
}
}
There are two basic patterns:
When dealing with RESTful network requests, we give all of our network routines a completion handler closure, which we call when the network request is done. That way, the caller can invoke each subsequent step in the completion handler of the prior step.
There are many variations on this theme (asynchronous Operation subclasses, futures/promises, etc), but the idea is the same, namely chaining a series of asynchronous tasks together in such a way that the caller can know when the requests are all done and can trigger the UI update.
On the other hand, when dealing with Firestore, we can add observers/listeners to update our UI as updates come in. The addSnapshotListener closure is repeatedly called as the underlying database is updated. In this scenario there isn’t a “ok, we’re done, update the UI” point in time (so we wouldn’t generally use the completion handler approach), but rather we just continually update the UI as the documents come in.
But while your example is using addSnapshotListener, it also is using the limit(to:), which adds a wrinkle. It’s a bit like the first scenario (e.g., if you’re limited to 8, and you retrieved 8, the listener won’t get called again). But it’s also a bit like the second scenario (e.g., if limiting to 8 and you currently have only 7 posts, it will retrieve the first seven and call that closure; but if another record comes in, it will call the closure again, this time with the 8th document as well).
Trying to handle both limited/paginated responses and listening for realtime updates can get complicated. I might suggest that if you want to make Firestore act like a RESTful service, I might suggest using getDocuments instead of addSnapshotListener, eliminating this complexity. Then you can use the completion handler approach recommended by others. It makes it behave a bit like the RESTful approach (but, then again, you lose the realtime update feature).
In case you’re wondering what the realtime, second scenario might look like, here is a simplified example (my post only has “text” and “date” properties, but hopefully it’s illustrative of the process):
func addPostsListener() {
db.collection("posts").addSnapshotListener { [weak self] snapshot, error in
guard let self = self else { return }
guard let snapshot = snapshot, error == nil else {
print(error ?? "Unknown error")
return
}
for diff in snapshot.documentChanges {
let document = diff.document
switch diff.type {
case .added: self.add(document)
case .modified: self.modify(document)
case .removed: self.remove(document)
}
}
}
}
func add(_ document: QueryDocumentSnapshot) {
guard let post = post(for: document) else { return }
let indexPath = IndexPath(item: self.posts.count, section: 0)
posts.append(post)
tableView.insertRows(at: [indexPath], with: .automatic)
}
func modify(_ document: QueryDocumentSnapshot) {
guard let row = row(for: document) else { return }
guard let post = post(for: document) else { return }
posts[row] = post
tableView.reloadRows(at: [IndexPath(row: row, section: 0)], with: .automatic)
}
func remove(_ document: QueryDocumentSnapshot) {
guard let row = row(for: document) else { return }
posts.remove(at: row)
tableView.deleteRows(at: [IndexPath(row: row, section: 0)], with: .automatic)
}
func row(for document: QueryDocumentSnapshot) -> Int? {
posts.firstIndex {
$0.id == document.documentID
}
}
func post(for document: QueryDocumentSnapshot) -> Post? {
let data = document.data()
guard
let text = data["text"] as? String,
let timestamp = data["date"] as? Timestamp
else {
return nil
}
return Post(id: document.documentID, text: text, date: timestamp.dateValue())
}
But this approach works because I’m not limiting the responses. If you do use limit(to:) or limit(toLast:), then you’ll stop getting realtime updates when you hit that limit.

How to sort data in Firebase?

I'm now able to sort posts and users by time.
My data structure looks like that:
posts
-postId
imageRatio:
imageUrl:
postText:
postTime:
uId:
users
-UserId
email:
profileImageURL:
radius:
uid:
username:
username_lowercase:
UPDATE
Now, I created a new class with all datas for the user and the posts:
class UserPostModel {
var post: PostModel?
var user: UserModel?
init(post: PostModel, user: UserModel) {
self.post = post
self.user = user
}
}
Declaration of my post array:
var postArray = [UserPostModel]()
Here, Im loading the datas into the new class:
self.observeRadius(completion: { (radius) in
let currentRadius = radius
// Üperprüfe, welche Posts im Umkreis erstellt wurden
let circleQuery = geoRef.query(at: location!, withRadius: Double(currentRadius)!)
circleQuery.observe(.keyEntered, with: { (postIds, location) in
self.observePost(withPostId: postIds, completion: { (posts) in
guard let userUid = posts.uid else { return }
self.observeUser(uid: userUid, completion: { (users) in
let postArray = UserPostModel(post: posts, user: users)
self.postArray.append(postArray)
print(postArray.post!.postText!, postArray.user!.username!)
self.postArray.sort(by: {$0.post!.secondsFrom1970! > $1.post!.secondsFrom1970!})
})
})
Here I'm loading the datas into the table view cells:
extension DiscoveryViewController: UITableViewDataSource {
// wie viele Zellen
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(postArray.count)
return postArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DiscoveryCollectionViewCell", for: indexPath) as! DiscoveryCollectionViewCell
cell.user = postArray[indexPath.row]
cell.post = postArray[indexPath.row]
//cell.delegate = self
return cell
}
}
Thanks in advance for your help!
There's a lot of code in the question and sometimes, simpler is better. So let's take a Post class, load the posts, get the associated user name and store it in an array. Then when complete, sort and print the posts in reverse chronological order.
A class to hold the post data and the user name
class PostClass {
var post = ""
var timestamp: Int! //using an int for simplicity in this answer
var user_name = ""
init(aPost: String, aUserName: String, aTimestamp: Int) {
self.post = aPost
self.user_name = aUserName
self.timestamp = aTimestamp
}
}
Note that if we want to have have both post data and user data we could do this
class PostUserClass {
var post: PostClass()
var user: UserClass()
}
but we're keeping it simple for this answer.
Then an array to store the posts
var postArray = [PostClass]()
and finally the code to load in all of the posts, get the associated user name (or user object in a full example).
let postsRef = self.ref.child("posts")
let usersRef = self.ref.child("users")
postsRef.observeSingleEvent(of: .value, with: { snapshot in
let lastSnapIndex = snapshot.childrenCount
var index = 0
for child in snapshot.children {
let childSnap = child as! DataSnapshot
let uid = childSnap.childSnapshot(forPath: "uid").value as! String
let post = childSnap.childSnapshot(forPath: "post").value as! String
let timestamp = childSnap.childSnapshot(forPath: "timestamp").value as! Int
let thisUserRef = usersRef.child(uid)
thisUserRef.observeSingleEvent(of: .value, with: { userSnap in
index += 1
//for simplicity, I am grabbing only the user name from the user
// data. You could just as easily create a user object and
// populate it with user data and store that in PostClass
// that would tie a user to a post as in the PostUserClass shown above
let userName = userSnap.childSnapshot(forPath: "Name").value as! String
let aPost = PostClass(aPost: post, aUserName: userName, aTimestamp: timestamp)
self.postArray.append(aPost) //or use self.postUserArray to store
// PostUserClass objects in an array.
if index == lastSnapIndex {
self.sortArrayAndDisplay() //or reload your tableView
}
})
}
})
and then the little function to sort and print to console
func sortArrayAndDisplay() {
self.postArray.sort(by: {$0.timestamp > $1.timestamp})
for post in postArray {
print(post.user_name, post.post, post.timestamp)
}
}
Note that Firebase is asynchronous so before sorting/printing we need to know we are done loading in all of the data. This is handled via the lastSnapIndex and index. The index is only incremented once each user is loaded and when all of the posts and users have been loaded we then sort and print as the data is complete.
This example avoids messy callbacks and completion handlers which may be contributing to the issue in the question - this piece of code is suspect and probably should be avoided due to the asynchronous nature of Firebase; the sort function is going to be called well before all of the users are loaded.
UserApi.shared.observeUserToPost(uid: userUid) { (user) in
self.postUser.append(user)
}
self.postUser.sort(by: {$0.postDate! > $1.postDate!})
*please add error checking.

Firebase Data flickers on addition of new values

I am making a social app to which I am fetching some data and flushing it to the collection view. I am flushing the all the posts from firebase to the posts array. I am also fetching the user information that posted the specific image. Both the database are 2 different models. Following is my data model :
posts
|- <post_id>
|- caption
|- ImageURL
|- views
|- spot
|- spot_id
|- sender<user_id>
|- spotted(value)
|- timestamp
|- author(<user_id>)
users
|- <user_id>
|- name
Following is the way I am fetching the post data in collectionVC and storing all to posts array:
func initialiseAllPostsContent(){
FBDataservice.ds.REF_CURR_USER.child("connections/following").observe(.childAdded) { (snapshot) in
if let snapshot = snapshot.value as? String {
self.followerKeys.append(snapshot)
}
}
if uid != nil {
self.followerKeys.append(uid!)
}
FBDataservice.ds.REF_POSTS.queryOrdered(byChild: "timestamp").observe(.childAdded, with: { (snapshot) in
print("post key is ", snapshot.key)
if let postDict = snapshot.value as? Dictionary<String, Any> {
let key = snapshot.key
if let postAuthor = postDict["author"] as? String {
for user in self.followerKeys {
if postAuthor == user {
let post = Posts(postId: key, postData: postDict)
self.posts.append(post)
}
}
}
}
})
reloadCollectionViewData()
}
func reloadCollectionViewData() {
FBDataservice.ds.REF_POSTS.queryOrdered(byChild: "timestamp").observe(.value) { (snapshot) in
self.collectionView.reloadData()
}
}
//I am updating the views on the post after a method is successfull. As soon as this is called, and then if like is pressed, views flicker
func updateViews(postid: String, views: Int) {
let viewref = FBDataservice.ds.REF_POSTS.child(postid)
let newviews = views + 1
viewref.updateChildValues(["views":newviews])
}
// fetching the user data from the post data
func getAllPosts(pid: String, completion: #escaping ((String) -> ())) {
FBDataservice.ds.REF_POSTS.child(pid).observeSingleEvent(of: .value) { (snapshot) in
if let snapshot = snapshot.value as? Dictionary<String, Any> {
if let userid = snapshot["author"] as? String {
completion(userid)
}
}
}
}
func getpostAuthorData(authorId : String, completion: #escaping (User) -> ()) {
FBDataservice.ds.REF_USERS.child(authorId).observeSingleEvent(of: .value) { (snapshot) in
if let snapshot = snapshot.value as? Dictionary<String, Any> {
if let userCredential = snapshot["credentials"] as? Dictionary<String, Any> {
completion(User(userid: authorId, userData: userCredential))
}
}
}
}
This is how I am assigning data in my cellForItemAtIndexPath
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
self.posts.sort(by: { $0.timestamp < $1.timestamp})
let post = posts[indexPath.row]
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as? SpotGroundCell {
cell.configureCellData(post: post)
getAllPosts(pid: post.postId) { (userid) in
self.getpostAuthorData(authorId: userid, completion: { (userdata) in
cell.configUserData(user: userdata)
})
}
return cell
} else {
return SpotGroundCell()
}
}
The code in my cell :
//Consider this as likes. I allow users to like multiple times. Once the model is loaded, it fetches all the spots according to the timestamp and then siplayer the most recent ones. Even this is doesn't display according to the current image and flickers. I replicate previous cell values even though I am refreshing the view.
var currentUserSpots = [Spot]() {
didSet {
self.currentUserSpots.sort(by: { $0.timestamp < $1.timestamp})
if !self.currentUserSpots.isEmpty {
self.emotionImage.image = UIImage(named: (self.currentUserSpots.first?.spotted)!)
self.emotionImage.alpha = 1
} else {
self.emotionImage.image = UIImage(named: "none")
self.emotionImage.alpha = 0.5
}
}
}
func configUserData(user: User) {
self.user = user
self.name.text = self.user.name
}
func configureCellData(post: Posts) {
print("Config is now called")
self.posts = post
self.caption.text = posts.caption
FBDataservice.ds.REF_POSTS.child(post.postId).child("spot").queryOrdered(byChild: "senderID").queryEqual(toValue: uid!).observeSingleEvent(of: .childAdded) { (snapshot) in
if let spotData = snapshot.value as? Dictionary<String, Any> {
let spot = Spot(id: snapshot.key, spotData: spotData)
if spot.spotted != nil {
self.currentUserSpots.append(spot)
}
}
}
}
Now whenever I am making a change or an event which updates the database(like updating a view). I see a flicker in the user object entities(such as name etc). That event also kills other processes and Notification Observers.
I scrapped the internet for the solutions, but by far just was able to find one, which doesn't solve my problem.
Any help will be greatly appreciated. I am really not sure where am I going wrong.
Whenever there is a change under REF_POSTS you right now:
delete all data from the view
re-add all data (including the change) to the view
Given that most changes will only affect one item in the list, you're making your view to N-1 more than is needed. This causes the flicker.
To solve this problem, you should listen to more granular information from the database. Instead of observing .value, add a listener for .childAdded. The completion block for this listener will be triggered whenever a new child is added, at which point you can just add the new child to your view.
FBDataservice.ds.REF_POSTS.queryOrdered(byChild: "timestamp").observe(.childAdded, with: { (snap) in
if let postDict = snap.value as? Dictionary<String, Any> {
let key = snap.key
if let postAuthor = postDict["author"] as? String {
for user in self.followerKeys {
if postAuthor == user {
let post = Posts(postId: key, postData: postDict)
self.posts.append(post)
}
}
}
}
})
As a bonus .childAdded also immediately fires for all existing child nodes, so you don't need the observer for .value anymore. I like keeping it myself though. As Firebase guarantees that it fires .value after all corresponding child* events, the .value event is a great moment to tell the view that all changes came in.
FBDataservice.ds.REF_POSTS.queryOrdered(byChild: "timestamp").observe(.value, with: { (snapshot) in
self.collectionView.reloadData()
})
You'll need a few more things for a complete implementation:
You should also observe .childChanged, .childMoved and childRemoved to handle those types of changes to the database.
Since a child may be added (or moved) anywhere in the list, you should actually use observe(_, andPreviousSiblingKey: ) to be able to put the item in the right spot in the list.

retrieve posts / query firebase swift 4

i am attempting to retrieve a list of Posts ("Planits - in my apps language") from firebase. My goal is to display a specific users posts within a table view on their profile. I have written a function to retrieves posts and query them by a sender ID so that the user see's their posts on their profile. But at the end of the query when i try to print out the appended array, i keep getting an empty array, so i can not go further on to populate the table view. Please any suggestions on where i went wrong, attached is a screen shot of my firebase nodes and the function i wrote. thanks
func retrievePost(){
ref = Database.database().reference()
let myPlanitsRef = self.ref.child("planits")
let query = myPlanitsRef.queryOrdered(byChild: "senderId").queryEqual(toValue: "uid")
print(query)
query.observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists() {
for child in snapshot.children {
let snap = child as! DataSnapshot
print(DataSnapshot.self)
let dict = snap.value as! [String: Any]
let myPostURL = dict["images"] as! String
self.images.append(myPostURL)
}
//print(myPostURL) - DOES NOT PRING ANYTHING
//print(self.images) - DOES NOT PRING ANYTHING
}
}) { (error) in
print(error)
}
}
override func viewDidLoad() {
super.viewDidLoad()
retrievePost()
print(images) // PRINTS []