Result Paging in Chat Client SDKs

Twilio's Programmable Chat client SDKs use paging to improve performance when accessing potentially large collections of chat objects.

Paginators

Public Channels, User Channels, and the Members list for each channel are exposed through Paginators. In Javascript, Messages are also paged using Paginators. When requesting the initial set of any of these entities, your provided callback mechanism will receive a result indicating the success or failure of the operation as well as a paginator to access the items.

While the signature of the individual methods will vary by SDK platform, each paginator has the following accessors:

  • A way to obtain the items
  • A boolean property indicating if there are subsequent pages
  • A method taking the same callback mechanism as the original call to request the next page
Loading Code Samples...
Language
private void getChannelsPage(Paginator<ChannelDescriptor> paginator) {

    // Add paginator.getItems() to UI

    if (paginator.hasNextPage()) {
        paginator.requestNextPage(new CallbackListener<Paginator<ChannelDescriptor>>() {
            @Override
            public void onSuccess(Paginator<ChannelDescriptor> channelPaginator) {
                getChannelsPage(channelPaginator);
            }
        });
    }
}

// User Channels
channelsObject = myClient.getChatClient().getChannels();

// Kick first get
channelsObject.getUserChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
    @Override
    public void onSuccess(Paginator<ChannelDescriptor> channelPaginator) {
        getChannelsPage(channelPaginator);
    }
});

// Channel Members
channel.getMembers().getMembers(new CallbackListener<Paginator<Member>>() {
  @Override
  public void onSuccess(Paginator<Member> memberPaginator) {
    // use memberPaginator.getItems() in UI
  }
});
// Get messages from the newest to oldest
channel.getMessages(20 /* by pages of 20 messages */ )
  .then(messagesPage => {
     messagesPage.items.forEach(message => {
       // do stuff for each message
     }
     // note, that by default pages are in "backwards" order,
     // so you should ask for previous page, not the next one
     if (messagesPage.hasPrevPage) {
       return messagesPage.prevPage(); // here we can ask for following page
     }
  });

// Get messages from the message with id 3 to newest
channel.getMessages(20, 3, 'forward')
  .then(messagesPage => { handle page the same way as above });

// Handle multiple pages
// Unfortunately, js has no asynchronous iterators yet, so there should be
// some boilerplate here. One of the way to go through all messages would be
// like this:
function processPage(page) {
  page.items.forEach(message => { do something for message });
  if (page.hasNextPage) {
    page.nextPage().then(processPage);
  } else {
    // all messages read!
  }
}
channel.getMessage(10, null, 'forward').then(processPage);
// If your subsequent pages will use the same completion
// handler, you can set the block as a variable to be
// referenced from within the block.
void __block (^_completion)();
TCHChannelDescriptorPaginatorCompletion completion = ^(TCHResult *result, TCHChannelDescriptorPaginator *paginator) {
    if (result.isSuccessful) {
        NSArray<TCHChannelDescriptor *> *items = [paginator items];
        // << consume the items in your UI >>

        // You can either obtain the next page immediately or wait
        // until the user scrolls to the bottom of the list before
        // requesting, depending on your use case.
        if (paginator.hasNextPage) {
            [paginator requestNextPageWithCompletion:completion];
        }
    } else {
        // report the error to the user
    }
};
_completion = completion;
[channelsList publicChannelsWithCompletion:completion];
Result Paging: Obtain a List of Public Channel Descriptors

Messages (Android, iOS)

The messages collection behaves a bit differently than channels and members since there is a temporal quality to how the objects are typically presented.

The messages collection objects offer the following ways to access items:

  • getLastMessages fetches the specified number of messages, starting with the most recent in the collection
  • getMessagesBefore fetches messages before (and including) the anchor message index specified
  • getMessagesAfter fetches messages after (and including) the anchor message index specified
  • messageWithConsumptionIndex fetches the message with the specified index or, if that message is no longer available, the message directly before it
  • messageWithIndex fetches the message specified by the index, if available
Loading Code Samples...
Language
// Fetch the initial messages
messagesObject.getLastMessages(BATCH_SIZE, new CallbackListener<List<Message>>()
{
  @Override
  public void onSuccess(List<Message> messagesArray) {
    // Display initial messages in your UI
  }
});

// ... user scrolls through messages list

// Fetch next set of messages when you get close to the
// end of your local messages
messagesObject.getMessagesBefore(firstMessage.getMessageIndex(), BATCH_SIZE,
                                 new CallbackListener<List<Message>>()
{
  @Override
  public void onSuccess(List<Message> messagesArray) {
    // Display latest messages in your UI
  }
});
// Fetch the initial messages
[self.channel.messages getLastMessagesWithCount:BATCH_SIZE
                                     completion:^(TCHResult *result, NSArray<TCHMessage *> *messages) {
                                        self.messages = messages;
                                        // << consume the initial items in your UI >>
                                      }];

// ... user scrolls through messages list

// Fetch next set of messages when you get close to the
// end of your local messages
[self.channel.messages getMessagesBefore:([firstMessage.index integerValue] - 1)
                               withCount:BATCH_SIZE
                              completion:^(TCHResult *result, NSArray<TCHMessage *> *messages) {
                                NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, messages.count)];
                                [self.messages insertObjects:messages
                                                   atIndexes:indexes];
                                 // << consume the latest items in your UI >>
                              }];
// Fetch the initial messages
channel.messages.getLastWithCount(BATCH_SIZE) { (result, messages) in
  self.messages = messages!
  // << consume the initial items in your UI >>
}

// ... user scrolls through messages list

// Fetch next set of messages when you get close to the
// end of your local messages
channel.messages.getBefore(UInt(self.messages.first!.index) - 1, withCount: BATCH_SIZE) { (result, messages) in
  self.messages.append(contentsOf: messages!)
  // << consume the latest items in your UI >>
}
Result Paging: Messages

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
private void getChannelsPage(Paginator<ChannelDescriptor> paginator) {

    // Add paginator.getItems() to UI

    if (paginator.hasNextPage()) {
        paginator.requestNextPage(new CallbackListener<Paginator<ChannelDescriptor>>() {
            @Override
            public void onSuccess(Paginator<ChannelDescriptor> channelPaginator) {
                getChannelsPage(channelPaginator);
            }
        });
    }
}

// User Channels
channelsObject = myClient.getChatClient().getChannels();

// Kick first get
channelsObject.getUserChannels(new CallbackListener<Paginator<ChannelDescriptor>>() {
    @Override
    public void onSuccess(Paginator<ChannelDescriptor> channelPaginator) {
        getChannelsPage(channelPaginator);
    }
});

// Channel Members
channel.getMembers().getMembers(new CallbackListener<Paginator<Member>>() {
  @Override
  public void onSuccess(Paginator<Member> memberPaginator) {
    // use memberPaginator.getItems() in UI
  }
});
// Get messages from the newest to oldest
channel.getMessages(20 /* by pages of 20 messages */ )
  .then(messagesPage => {
     messagesPage.items.forEach(message => {
       // do stuff for each message
     }
     // note, that by default pages are in "backwards" order,
     // so you should ask for previous page, not the next one
     if (messagesPage.hasPrevPage) {
       return messagesPage.prevPage(); // here we can ask for following page
     }
  });

// Get messages from the message with id 3 to newest
channel.getMessages(20, 3, 'forward')
  .then(messagesPage => { handle page the same way as above });

// Handle multiple pages
// Unfortunately, js has no asynchronous iterators yet, so there should be
// some boilerplate here. One of the way to go through all messages would be
// like this:
function processPage(page) {
  page.items.forEach(message => { do something for message });
  if (page.hasNextPage) {
    page.nextPage().then(processPage);
  } else {
    // all messages read!
  }
}
channel.getMessage(10, null, 'forward').then(processPage);
// If your subsequent pages will use the same completion
// handler, you can set the block as a variable to be
// referenced from within the block.
void __block (^_completion)();
TCHChannelDescriptorPaginatorCompletion completion = ^(TCHResult *result, TCHChannelDescriptorPaginator *paginator) {
    if (result.isSuccessful) {
        NSArray<TCHChannelDescriptor *> *items = [paginator items];
        // << consume the items in your UI >>

        // You can either obtain the next page immediately or wait
        // until the user scrolls to the bottom of the list before
        // requesting, depending on your use case.
        if (paginator.hasNextPage) {
            [paginator requestNextPageWithCompletion:completion];
        }
    } else {
        // report the error to the user
    }
};
_completion = completion;
[channelsList publicChannelsWithCompletion:completion];
// Fetch the initial messages
messagesObject.getLastMessages(BATCH_SIZE, new CallbackListener<List<Message>>()
{
  @Override
  public void onSuccess(List<Message> messagesArray) {
    // Display initial messages in your UI
  }
});

// ... user scrolls through messages list

// Fetch next set of messages when you get close to the
// end of your local messages
messagesObject.getMessagesBefore(firstMessage.getMessageIndex(), BATCH_SIZE,
                                 new CallbackListener<List<Message>>()
{
  @Override
  public void onSuccess(List<Message> messagesArray) {
    // Display latest messages in your UI
  }
});
// Fetch the initial messages
[self.channel.messages getLastMessagesWithCount:BATCH_SIZE
                                     completion:^(TCHResult *result, NSArray<TCHMessage *> *messages) {
                                        self.messages = messages;
                                        // << consume the initial items in your UI >>
                                      }];

// ... user scrolls through messages list

// Fetch next set of messages when you get close to the
// end of your local messages
[self.channel.messages getMessagesBefore:([firstMessage.index integerValue] - 1)
                               withCount:BATCH_SIZE
                              completion:^(TCHResult *result, NSArray<TCHMessage *> *messages) {
                                NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, messages.count)];
                                [self.messages insertObjects:messages
                                                   atIndexes:indexes];
                                 // << consume the latest items in your UI >>
                              }];
// Fetch the initial messages
channel.messages.getLastWithCount(BATCH_SIZE) { (result, messages) in
  self.messages = messages!
  // << consume the initial items in your UI >>
}

// ... user scrolls through messages list

// Fetch next set of messages when you get close to the
// end of your local messages
channel.messages.getBefore(UInt(self.messages.first!.index) - 1, withCount: BATCH_SIZE) { (result, messages) in
  self.messages.append(contentsOf: messages!)
  // << consume the latest items in your UI >>
}