25

Firebase Model

This is my model.

messagesRef = Firebase(url: "https://"+CONSTANTS.FirebaseDB+".firebaseio.com/messages/1:1000")
    messagesRef.queryLimitedToLast(5).observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) in
        self.message_list.append(snapshot) // it works.
    });
});

My code works -- it gets the last 5 messages (8-12).

However, what if I have a function query the next 5 messages (2-6)? With a start and offset. How can I query this?

TIMEX
  • 259,804
  • 351
  • 777
  • 1,080
  • the startAt() and endAt() modifiers haven’t worked out? https://www.firebase.com/blog/2013-10-01-queries-part-one.html – kpie Apr 12 '15 at 20:14
  • iOS library doesn't have those methods. – TIMEX Apr 12 '15 at 23:30
  • The firebase-util library supports pagination: https://github.com/firebase/firebase-util/blob/master/src/Paginate/README.md. The core of the implementation is available here: https://github.com/firebase/firebase-util/blob/master/src/Paginate/libs/Paginate.js. Although the library is availably only in JavaScript, the approach used should be readily transferrable to Firebase's iOS SDK. – Frank van Puffelen Apr 13 '15 at 04:12
  • If your keys are indeed as sequential as the screenshot seems to indicate, the approach @kpie suggest should also work. But note that such sequential ordered keys are often a bad idea in distributed/multi-user systems, since/when multiple users/clients may be adding items to the array at the same time. See https://www.firebase.com/docs/ios/guide/understanding-data.html#section-arrays-in-firebase – Frank van Puffelen Apr 13 '15 at 04:16
  • iOS certainly has query methods, and even guide to introduce them: https://www.firebase.com/docs/ios/guide/retrieving-data.html#section-complex-queries – Kato Apr 13 '15 at 19:46
  • You should look here https://www.youtube.com/watch?v=3YuRRb8driU – Nike Kov Oct 30 '17 at 21:42

4 Answers4

20
messagesRef = Firebase(url: "https://"+CONSTANTS.FirebaseDB+".firebaseio.com/messages/1:1000")messagesRef
.queryOrderedByKey()
.queryStartingAtValue(5)
.queryEndingAtValue(10)
.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) in self.message_list.append(snapshot) });

This is kind of a shot in the dark but it seems like it should work based on documentation here https://www.firebase.com/docs/ios-api/Classes/Firebase.html#//api/name/queryStartingAtValue:

kpie
  • 9,588
  • 5
  • 28
  • 50
19

On spending too much time I have figured it out and here is the solution. This is Objective-C code you can convert it into swift. Call below function for paging purpose.

- (void)loadMoreMessages {
    
    if (!lastMessageKey) {
        // Loading messages first time
        [[[msgsReference queryOrderedByKey] queryLimitedToLast:K_MESSAGES_PER_PAGE] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
            if (snapshot.exists) {
                
                for (FIRDataSnapshot *child in snapshot.children) {
                   
                    NSMutableDictionary *dict = [child.value mutableCopy];
                    [dict setObject:child.key forKey:@"id"];
                    [messages addObject:dict];
                }
                
                lastMessageKey = [[snapshot.children.allObjects firstObject] key];
                NSLog(@"%@", messages);
            }
        }];
    }
    else {
        // Paging started
        [[[[msgsReference queryOrderedByKey] queryLimitedToLast:K_MESSAGES_PER_PAGE + 1] queryEndingAtValue:lastMessageKey] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
           
            if (snapshot.exists) {
       
                NSInteger count = 0;
                NSMutableArray *newPage = [NSMutableArray new];
                for (FIRDataSnapshot *child in snapshot.children) {
                    
                    // Ignore last object because this is duplicate of last page
                    if (count == snapshot.childrenCount - 1) {
                        break;
                    }
                    
                    count += 1;
                    NSMutableDictionary *dict = [child.value mutableCopy];
                    [dict setObject:child.key forKey:@"id"];
                    [newPage addObject:dict];
                }
                
                lastMessageKey = [[snapshot.children.allObjects firstObject] key];

                // Insert new messages at top of old array
                NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange: NSMakeRange(0, [newPage count])];
                [messages insertObjects:newPage atIndexes:indexes];
                NSLog(@"%@", messages);
            }
        }];
    }
}

And here is description of objects you needed:

#define K_MESSAGES_PER_PAGE 50 // A macro defining the numbers in one request
msgsReference // Firebase database messages node reference I'm also attaching the screenshot of my db structure for make you more clear
lastMessageKey // Is a NSString object which store the first key of last page
messages // Is a NSMutableArray storing the result

Good Luck!! (y)

enter image description here

Community
  • 1
  • 1
TheTiger
  • 13,264
  • 3
  • 57
  • 82
  • 2
    Great answer. This should have been accepted as answer. – Tripti Kumar Nov 29 '16 at 08:07
  • what is lastMessageKey? how to store the first key of last page in lastMessageKey? – Himali Shah Nov 30 '16 at 03:50
  • @HimaliShah This is a `NSString` object and what value we need to assign it is already mentioned in code. Check this line `lastMessageKey = [[snapshot.children.allObjects firstObject] key];` – TheTiger Nov 30 '16 at 06:01
  • how would you do this for a "feed" of all the "friends"? currently i'm gathering the IDs of all the friends and then a request on `firebase.child(id).child(userposts)`, but i have no idea how to do paging here – David Seek Dec 22 '16 at 15:35
  • @DavidSeek - Consider `chat_xyz` are your `friends` and `messages` are `userposts` – TheTiger Dec 24 '16 at 08:54
  • @TheTiger but i guess your logic just works if i'm downloading the `userposts` from `a friend`, not for example from an `array` of friends – David Seek Dec 24 '16 at 16:27
  • let's say i have user `a, b, c & d` -> i'm friends with `c & d` and want a feed with pagination from their posts... – David Seek Dec 24 '16 at 19:22
  • I would like to point out that this downloads the first loaded content over and over again. It does not paginate. – Hobbyist Apr 23 '17 at 14:48
  • @Hobbyist Probably you are not assigning the new `key` to `lastMessageKey`. i.e. everytime `if` case is executing. – TheTiger Apr 24 '17 at 08:18
  • @TheTiger I was, The problem was I actually had to use the `lastObject`, and not `firstObject` as demonstrated in your code. – Hobbyist Apr 26 '17 at 11:40
  • Really helpful and solved pagination issue. Thanks Tiger! :) – NightFury Jan 26 '18 at 07:49
0

Swift 3.x

func fetchEarlierMessages(chatGroupId: String, messageKey: String, completion: @escaping ([Message]?) -> ()) {
    previousMessageRef = root.child("messages").child(chatGroupId)
    messageValueHandle = previousMessageRef.queryOrderedByKey().queryLimited(toLast: 10).queryEnding(atValue: messageKey).observe(.value, with: { snapshot in
        var messages: [Message] = []
        for child in snapshot.children.allObjects as! [FIRDataSnapshot] {
            guard var item = child.value as? [String: AnyObject] else { return }
            item["message_id"] = child.key as AnyObject
            if let message = self.parseMessage(snapshot: child) {
                messages.append(message)
            }
        }
        messages.removeLast()
        completion(messages)
    })
}

Here the 'parseMessage' function is my custom function to convert snapshot into message model, you can use your own. the message key is the key of earliest message you loaded in initial firebase call.

Nits007ak
  • 743
  • 7
  • 15
-2

Swift 2017

I'd like to present an as elegant solution as i could made for this task of pagination with the firebase. This method called both when initializing and loading more data:

var items: [ItemEntity] = []
var theEndOfResults = false
var lastLoadedItem: ItemEntity? {
    return items.last
}
    
func loadItems() {
    let isLoadingMore = lastLoadedItem != nil
        
    Decorator.showStatusBarLoader()
    self.databaseReference
        .child("items")
        .queryOrdered(byChild: "date")
        .queryEnding(atValue: isLoadingMore ? lastLoadedItem!.date.stringValue : Date.distantFuture.stringValue)
        .queryLimited(toLast: 5)
        .observeSingleEvent(of: .value) { snapshot in
            var items = self.array(from: snapshot)
                .map { ItemEntity(parentKey: $0.parentKey, dictionary: $0.dict) }

                self.theEndOfResults = (self.lastLoadedItem == items.last) // prevent loading when it's no more items
                if isLoadingMore { items.removeFirst() } // removing as the firebase sending a repeated one
                self.items.append(contentsOf: items)

                self.reloadData()
        }
    }

The function for reloading data in controller.

override func reloadData() {
    tableV.reloadData()
    refreshControl.endRefreshing()
    tableV.loadControl?.endLoading()
    Decorator.hideStatusBarLoader()
}

This is called when a user reach the end of tableView.

@objc public func loadMore() {
    guard self.theEndOfResults == false else { tableV.loadControl?.endLoading(); return }
    self..loadItems()
}

Making array from snapshot

func array(from snapshot: DataSnapshot) -> [ParseResult] {
    var items = [ParseResult]()
    if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
        for snap in snapshots {
            if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
                items.append((snap.key, postDictionary))
            }
        }
    }
    print(" DATA COME:\r\(snapshot)")
    //or u can use: dump(items, indent: 2, maxDepth: 5, maxItems: 15)
    return items.reversed()
}

Thanks this video for clarifying some moments with "cool" firebase reference api.
Good luck in development and ask any questions if found something unclear.

Community
  • 1
  • 1
Nike Kov
  • 12,630
  • 8
  • 75
  • 122
  • This is not a Pagination feature - since to reload the tableview again when it will load probably again 100 elements in the tableview - you need to load only the new ones – FBC Aug 10 '18 at 06:41