FlexibleAdapter
FlexibleAdapter copied to clipboard
Endless Scroll Feature Request
I am creating a messaging screen and am using the Endless Scroll Listener to lazy load old messages. Here is my problem. Currently the callback for onLoadMore
is only called when you hit the last item minus the threshold. This works great in most cases but imagine this scenario:
Message API Limit = 10 messages
I am saving my messages to disk. I have a conversation with 50 messages in it. Then I don't use the app for months. Let's say 50 more messages have been sent to me since I last opened the app. I now have 100 total messages in the server. I open the app again and go to this conversation. The 50 messages that were saved to disk will show. Then I will pull down the latest 10 messages, but I won't try loading more messages until I reach the end of the list. So I have in effect missed out on 40 messages. (10 messages from initial load, plus the 50 stored in the database)
Is there a way to be notified every time I scroll 10 messages so I can download the next 10 messages, whether they are needed to be downloaded or not? Is there a better way to solve this problem that I am not seeing?
I have used the library for a similar project. I don't have the exact code with me anymore but for your particular scenario, there are 2 ways to solve this:
- You should change your message server API to accept a cursor/timestamp/page of the last request made and to identify that there is a gap between this request and last request that's larger than the API paging limit. When this gap is detected, you should return next 10 messages on the server that immediately follow the last request, rather than returning 10 most recent messages. Messages are not "What's New" contents, they are contextual and should always be presented as a timeline; in other words, your API can't just return 10 latest messages for every request, that doesn't make sense in the real world.
OR
- Suppose you're using RecyclerView, it has an
OnScrollListener
(can't remember the full name) you can implement to get notified every time the view port (of the recycler/list) changes. OnScrollListener isn't managed by this library so you may need to do some extra work. Because your API doesn't really work the same way as traditional endless scrolling does, get rid of this library's endless scroll feature and implement the listener on your own. You can have it as simple as (pseudo-code):
// Outside your listener:
let lastIndex = adapter.layoutManager.firstVisibleItemIndex;
...
// Inside your listener:
let currentIndex = adapter.layoutManager.lastVisibleItemIndex;
if (currentIndex - lastIndex > 10) {
loadSomeData();
lastIndex = currentIndex;
}
Thanks @VaslD! Timestamps is a good idea. I ended up taking out the Endless listener as you suggested and did something like this in my FlexibleItem
@Override
public void bindViewHolder(FlexibleAdapter adapter, SpeechBubbleViewHolder holder, int position,
List payloads) {
if ((position + NetworkConstants.MESSAGE_THRESHOLD) % (NetworkConstants.MESSAGES_LIMIT) == 0) {
int offset = position + NetworkConstants.MESSAGE_THRESHOLD;
Timber.d("Load more items... at position %s. Offset to load: %s", position, offset);
messageRepository.syncMessages(message.getGroupId(), offset);
}
holder.speechBubbleView.setViewModel(new SpeechBubbleViewModel(message));
}
That got me super close, but it will be called more than once as you are scrolling, so just added a check where I only load the offset
for that thread once per session. Not ideal, but good enough for now.
private HashMap<String, HashSet<Integer>> pagedGroups = new HashMap<>();
public void syncMessages(@NonNull String groupId, int offset) {
Timber.v("Syncing Messages for Group %s with offset %s", groupId, offset);
if (pagedGroups.get(groupId) == null) {
pagedGroups.put(groupId, new HashSet<>());
}
if (!pagedGroups.get(groupId).contains(offset)) {
Timber.i("We have not loaded the offset %s for group %s yet. Starting Job Now.", offset, groupId);
pagedGroups.get(groupId).add(offset);
jobManager.addJobInBackground(new GetMessagesForGroupJob(groupId, offset));
} else {
Timber.d("We have already loaded the offset %s for group %s.", offset, groupId);
}
}