0

I am trying to implement pagination into my app (infinite scrolling) but I don't seem to wrap my head around the logic. I've gone as far as reading the first 20 documents, then when I scroll the "Read more posts" method is called which reads The whole remaining documents (80 in this case) which is not what I want. I want to read 20 posts at a time. Here is my code please point out at what I am doing wrong:

import UIKit
import FirebaseAuth
import Firebase

class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{

var following = [String]()

var posts = [Post]()
var fetchingMore = false

override func viewDidLoad() {
    super.viewDidLoad()
    
// reads first 20 posts
    let db = Firestore.firestore()
   
    db.collection("Posts").addSnapshotListener {
        snapshot , error in

        snapshot!.documentChanges.forEach { diff in
            if (diff.type == .added) {
                self.readPosts()
            }
        }
    }
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let height = scrollView.frame.size.height
    let contentYoffset = scrollView.contentOffset.y
    let distanceFromBottom = scrollView.contentSize.height - contentYoffset
    if distanceFromBottom < height {
        if(!fetchingMore){
            print("aaah")
            beginBatchFetch()
            return
        }
    }
}

func beginBatchFetch(){
    fetchingMore = true
    readMorePosts()
    fetchingMore = false
}


public func readPosts(){
    getFollowingList { following in
        let db = Firestore.firestore()
        
        db.collection("Posts").whereField("userId", in: following).order(by: "timestamp").limit(toLast: 20).addSnapshotListener{
            posts , error in
            
            if (error != nil){
                print("253 \(error!.localizedDescription)")
                return
            }
            else{
                var tempPosts = [Post]()
                for doc in posts!.documents{
                    let daate = doc.data()["timestamp"] as! Timestamp
                    let mydbl = daate.dateValue().timeIntervalSince1970 * 1000

                    let post = Post(id: doc.data()["postId"] as! String, username: "@\(doc.data()["username"]!)", userId: doc.data()["userId"] as! String , text: doc.data()["text"] as! String, timestamp: mydbl , numLikes: 0, numReposts: 0)

                    let isDuplicate = tempPosts.contains(where: { $0.id == post.id })

                    if(!isDuplicate){
                        tempPosts.insert(post, at: 0)
                    }
                } // end for

                self.posts = tempPosts
                    self.tableView.reloadData()
            }
        }
    }
}

func getFollowingList(_ completion: @escaping (_ followingList:[String]) -> Void){
    let db = Firestore.firestore()
    
    db.collection("Following").document(Auth.auth().currentUser!.uid).collection("Follows").addSnapshotListener {
        followingSnapshot , error in
        
        if(error != nil){
            print("243 \(String(describing: error?.localizedDescription))")
            return
        }
        
        else{
            var tempFollowing = [String]()
            
            for following in followingSnapshot!.documents{
                tempFollowing.append(following.documentID)
            }
            return completion(tempFollowing)
        }
    }
}
}

func readMorePosts(){
    getFollowingList { following in
        let db = Firestore.firestore()
    
    let first = db.collection("Posts").whereField("userId", in: following).order(by: "timestamp", descending: true).limit(to: 20)

    first.addSnapshotListener { (snapshot, error) in
        guard let snapshot = snapshot else {
            print("134 \(error?.localizedDescription)")
            return
        }
        
        guard let lastSnapshot = snapshot.documents.last else {
            return
        }
       
        // paginating ..
        let next = db.collection("Posts").whereField("userId", in: following)
            .order(by: "timestamp", descending: true)
            .start(afterDocument: lastSnapshot)
            .addSnapshotListener {
                docs , err in
                
                if err != nil{
                    print("152 \(err!.localizedDescription)")
                    return
                }
                else{
                    for doc in docs!.documents{
                        let daate = doc.data()["timestamp"] as! Timestamp
                        let mydbl = daate.dateValue().timeIntervalSince1970 * 1000

                        let post = Post(id: doc.data()["postId"] as! String, username: "@\(doc.data()["username"])" as! String, userId: doc.data()["userId"] as! String , text: doc.data()["text"] as! String, timestamp: mydbl , numLikes: 0, numReposts: 0)

                        let isDuplicate = self.posts.contains(where: { $0.id == post.id })

                        if(!isDuplicate){
                            self.posts.append(post)
                        }
                    } // end for
                        self.tableView.reloadData()
                }
            }
    }
}
}
  • Why are you using a snapshot listener for these paginated results? You can, but it's more complicated than simple pagination. When you paginate realtime results, you have to understand that the snapshot listener is only listening for changes in the first page. And when it comes time to refresh the UI, you have to figure out how many results the user has already paginated and then load that many. Do you need these results to be updated in realtime or would a regular document-get work here? Paginating static results is a much simpler process. – trndjc Sep 02 '22 at 04:48
  • Here is my [answer](https://stackoverflow.com/a/73577294/17286292) about FireStore pagination. Please check it. – Neklas Sep 06 '22 at 07:46

2 Answers2

1

If you want to limit the number of results, you need to include a limit clause in your query:

let next = db.collection("Posts").whereField("userId", in: following)
    .order(by: "timestamp", descending: true)
    .start(afterDocument: lastSnapshot)
    .limit(20)
    .addSnapshotListener {

You'll need to execute a query like this for every page, with an updated value for lastSnapshot each time.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • This only gets me the next 20 documents then stops from getting the rest – Yousef Baghlaf Sep 02 '22 at 03:31
  • You said: "I want to read 20 posts at a time" which is what this should do. If you're not getting the 20 posts after that, your `lastSnapshot` value is not pointing to the correct document. Set a breakpoint on the line with the query, run in the debugger, and check the value of each variable. – Frank van Puffelen Sep 02 '22 at 03:43
  • In the code above, the lastSnapshot is referring to the last snap shot of “first” query. Wouldn’t it always be the same value since it’s being initialized after reading the 20 top posts? – Yousef Baghlaf Sep 02 '22 at 03:52
  • That's something you can check in the debugger as I commented above. If `lastSnapshot` indeed always is the same value, that explains why you're not getting different results. Keep in mind, you posted "which reads The whole remaining documents (80 in this case) which is not what I want. I want to read 20 posts", which is what I showed how to do by adding a `limit(20)` to your query. – Frank van Puffelen Sep 02 '22 at 04:01
  • "... at a time" – Yousef Baghlaf Sep 02 '22 at 04:08
  • 1
    I'm just not sure how to help further Yousef. I showed how to limit the query to 20 documents, and explained what is needed to get the next page (passing in the last item of the previous page as your `lastSnapshot`). – Frank van Puffelen Sep 02 '22 at 07:03
0

Here is my answer for FireStore pagination.

I think it can solve your problem.

Neklas
  • 504
  • 1
  • 9