How can I detect firebase observe childAdded is stop calling when reach queryLimit?

1k Views Asked by At

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!

2

There are 2 best solutions below

12
On BEST ANSWER

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()
             }

        }


    } 

    }

}
2
On
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()
                }
            }
        })
    } 
}