Does Firebase know when data is no longer a part of query? - swift

In my Firebase database, I have children containing Unix times, and I'm querying by times that occur after the current time. I'm putting the data of each child which matches that criteria into a UICollectionView. When the current time surpasses the time of one of the children, I want for the child to expire, and to get removed from the UICollectionView. Currently, it isn't getting removed until I restart the app. Here is some of the code:
// in viewDidLoad
self.events_query = Database.database().reference().child("events").queryOrdered(byChild: "end-time").queryStarting(atValue: Date().timeIntervalSince1970)
// in viewWillAppear
func observeAllEvents() {
self.events_query.observe(.value, with: { (snapshot) in
guard let eids_dict = snapshot.value as? [String : AnyObject] else { return }
let eids = Array(eids_dict.keys)
for eid in eids {
print(eid)
}
Event.getAllEvents(with: eids, ref: self.events_query.ref, completion: { (events) in
if let feed_datasource = self.datasource as? EventFeedDatasource {
feed_datasource.events = events
}
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
})
})
}
// in viewDidDisappear
self.events_query.removeAllObservers()
Here's the function getAllEvents:
static func getAllEvents(with eids: [String], ref: DatabaseReference, completion: #escaping (_ events: [Event]) -> Void) {
var events = [Event]()
let dispatch_groups = [DispatchGroup(), DispatchGroup()]
for eid in eids {
dispatch_groups[0].enter()
ref.child(eid).observeSingleEvent(of: .value, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String : AnyObject] else { return }
dispatch_groups[1].enter()
// I'm not including `load` because all it does is parse the snapshot
Event.load(with: dictionary, completion: { (event) in
events.append(event)
dispatch_groups[1].leave()
})
dispatch_groups[0].leave()
})
}
dispatch_groups[0].notify(queue: DispatchQueue.main) {
dispatch_groups[1].notify(queue: DispatchQueue.main) {
completion(events)
}
}
}

Related

Restart Firebase observe

I created a method where I fetch all the published Books in Firebase. Each Book object stores a userId string value. I would like to fetch all books excluding currentUser books. I fetch 5 books every time I call the method starting from lastBookId, however if a user publishes more than 5 books, and are the first five, they can not be shown and as a result can not continue fetching them. I was thinking about increasing the limit value and calling the query observation again.
My code:
public func fetchBooksStarting(with lastBookId: String? = nil, completion: #escaping (Result<[Book], Error>) -> Void) {
var limit: UInt = 5
var books = [Book]()
let group = DispatchGroup()
var query = database.child("books").queryOrdered(byChild: "type")
if lastBookId != nil {
query = query.queryStarting(afterValue: BookType.Fiction.rawValue, childKey: lastBookId)
} else {
query = query.queryEqual(toValue: BookType.Fiction.rawValue)
}
query.queryLimited(toFirst: limit).observeSingleEvent(of: .value, with: { snap in
guard let snapshot = snap.children.allObjects as? [DataSnapshot] else {
completion(.failure(DatabaseErrors.failedToFetch))
return
}
books.removeAll()
for data in snapshot {
group.enter()
if let dict = data.value as? [String: AnyObject] {
let book = Book(dict: dict, bookId: data.key)
if book.userId == currentUserUid {
limit += 1
// recall query observe
} else {
books.append(book)
}
}
group.leave()
}
group.notify(queue: .main) {
completion(.success(books))
}
}, withCancel: nil)
}

Checking a null refrence Firebase swift

I have a application where I get values from a reference. The issue I have now is at times the reference might not the present and if the refrence is not present, the user is just suppose to be in idle state. How ever, I do not know how to achieve this task as the particular refrence could be absent from the firebase.
Below is my code for checking the ref data
func refreshActiveTrip() -> Observable<Trip> {
guard let trip = getCurrentTrip() else {
return Observable.error(RxError.noElements)
}
tripRef = Database.database().reference(forTransportChampionId: (getChampion()?.id)!, tripId: trip.id!)
return Observable<Trip>.create({ (observer) -> Disposable in
let disposable = Disposables.create {
self.tripRef?.removeAllObservers()
}
self.tripRef?.observe(.value, with: { (snapshot) in
if let data = snapshot.value as? [String: AnyObject] {
let trip = Trip(dictionary: data as NSDictionary)
self.saveCurrentTrip(trip)
if !disposable.isDisposed {
observer.onNext(trip)
}
}
})
return disposable
})
}
tripRef could be null i.e it does not exsit, how can i check this and an idle value
How about using the exists()
FIRDatabase.database().reference()
ref.child("the_path").observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.exists(){
print("It exists!!")
}else{
print("Nope, doesn't exist!")
}
})
Firebase Doc Reference

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

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.

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 detect firebase observe childAdded is stop calling when reach queryLimit?

Now, I'm so confused about firebase with observe using childAdded data event type. The reason why I use childAdded to observe my firebase because I want to make my list page dynamic whether firebase has new data insert.
And my question is how to know observe is stop calling when reach the queryLimit? Because I have a indicator and I want to turn it off when reach the queryLimit.
My firebase structure below:
root {
talkID1(id by auto create) {
attribute1 ...
attribute2 ...
attribute3 ...
time
}
talkID2(id by auto create){
...
time
}
... // many talk ID which auto create by firebase
}
As what I know, if using childAdd to observe, data will one child by child to passing data in call back. So If I have N datas in firebase and I think it will calls N<=5 times, right?
My completion handler below:
func receiveIfExist(completion: #escaping (_ data: (My data type) -> Void) {
let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")
requestWithQuery.queryLimited(toLast: 5).observe(.childAdded, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }
self.getSingleTalkInfo(key: snapshot.key, value: value, completion: { (data) in
completion(data)
})
})
}
I'm calling receiveIfExist this function in viewDidLoad().
override func viewDidLoad() {
super.viewDidLoad()
self.myIndicator.startAnimating() // start running indicator
self.receiveIfExist { (data) in
self.handleTalk(with: data) // do something with request data
self.myIndicator.stopAnimating() // WEIRD!!!! Please look comments below
/*
I think it can not be added here because this completion will call N<=5 times just what I said before.
I think it should detect what my queryLimit data is and check the request data is this queryLimit data or not.
If yes then stop indicator animating, if not then keep waiting the queryLimit reach.
*/
}
}
How can I detect the observe is reach queryLimit?
If I can detect then I can turn off my indicator when it reach.
Thank you!
queryLimited(toLast: 5)
means (in much simpler words) please get the last 5 values (order is decided by the previous part of your query)
1. Now, since you are sorting the data by times , the values with the last 5 times will be retrieved, therefore your observer will be triggered 5 times
2. Note that if you have less than 5 records say 2 records, then it will be triggered only twice because maximum limit is 5, not minimum limit
3. Another point is that say if a new child is added and when you sort the items again according to the time and the new child is one of the last 5 items then this observer will be triggered again.
so to get the query limit you can make some changes in your code like this:
func receiveIfExist(completion: #escaping (data: YourType, limitCount: Int) -> Void) {
let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")
requestWithQuery.queryLimited(toLast: 5).observe(.childAdded, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }
self.getSingleTalkInfo(key: snapshot.key, value: value, completion: { (data) in
self.index = self.index + 1
completion(data, self.index)
})
})
}
Then using the above function as follows:
var index = 0
override func viewDidLoad() {
super.viewDidLoad()
self.myIndicator.startAnimating() // start running indicator
self.receiveIfExist { (data, limitCount) in
self.handleTalk(with: data) // do something with request data
if limitCount == 5 {
self.myIndicator.stopAnimating()
}
}
}
UPDATED:
Since very good point raised by Kevin, that above solution will fail if we have say only two records and index will never be equal to 5 and myIndicator will not stop animating,
One solution that comes to my mind is this:
First we get the children count using observeSingleEvent:
func getChildrenCount(completion: #escaping (_ childrenCount: Int) -> Void){
Database.database.reference().child("root").observeSingleEvent(of:.value with: { (snapshot) in
completion(snapshot.children.count)
}
}
then we apply the query to get last 5 items:
func receiveIfExist(completion: #escaping (data: YourType, limitCount: Int) -> Void) {
let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")
requestWithQuery.queryLimited(toLast: queryLimit).observe(.childAdded, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }
self.getSingleTalkInfo(key: snapshot.key, value: value, completion: { (data) in
self.index = self.index + 1
completion(data, self.index)
})
})
}
then use this count in your code as follows:
var index = 0
var childrenCount = 0
var queryLimit = 5
override func viewDidLoad() {
super.viewDidLoad()
self.myIndicator.startAnimating() // start running indicator
self.getChildrenCount {(snapChildrenCount) in
self.childrenCount = snapChildrenCount
self.receiveIfExist { (data, limitCount) in
self.handleTalk(with: data) // do something with request data
if (self.childrenCount < self.queryLimit && limitCount == self.childrenCount) || limitCount == self.queryLimit {
DispatchQueue.main.async {
self.myIndicator.stopAnimating()
}
}
}
}
}
func receiveIfExist(limitCount: UInt, completion: #escaping (data: MyDataType) -> Void) {
let requestWithQuery = Database.database.reference().child("root").queryOrdered(byChild: "time")
requestWithQuery.queryLimited(toLast: limitCount).observe(.childAdded, with: { (snapshot) in
guard let value = snapshot.value as? [String: Any] else { return }
self.getSingleTalkInfo(key: snapshot.key, value: value, completion: { (data) in
completion(data)
})
})
}
I also do this function for only observe single child's value
let requestTalks = Database.database.reference().child("root")
func getSingleTalk(by key: String = "", at limitType: TalkLimitType, completion: #escaping (_ eachData: MyDataType) -> Void) {
var requestSingleTalk: DatabaseQuery {
switch limitType {
case .first :
return self.requestTalks.queryOrdered(byChild: "time").queryLimited(toFirst: 1)
case .last :
return self.requestTalks.queryOrdered(byChild: "time").queryLimited(toLast: 1)
case .specificKey :
return self.requestTalks.child(key)
}
}
requestSingleTalk.observeSingleEvent(of: .value, with: { (snapshot) in
if limitType == .specificKey {
guard let value = snapshot.value as? [String: Any] else { return }
self.getSingleTalkInfo(key: snapshot.key, value: value, completion: { (data) in
completion(data)
})
} else {
guard let snapshotValue = snapshot.value as? NSDictionary,
let eachTalkKey = snapshotValue.allKeys[0] as? String,
let eachTalkValue = snapshotValue.value(forKey: eachTalkKey) as? [String: Any] else { return }
self.getSingleTalkInfo(key: eachTalkKey, value: eachTalkValue, completion: { (data) in
completion(data)
})
}
})
}
As a result, I can do something like this in my viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
self.myIndicator.startAnimating()
self.receiveIfExist(limitCount: 5) { (eachData) in
self.handleTalk(with: eachData)
self.getSingleTalk(at: .last, completion: { (lastData) in
if eachData.keyID == lastData.keyID{
DispatchQueue.main.async {
self.myIndicator.stopAnimating()
}
}
})
}
}