Shadow
Shadow

Reputation: 4782

Paging 3: How to load list at item position or at item with specific id

I have a list of messages. Each message has a unique GUID.

My setup is working for normal usage: user clicks on conversation, list opens with all the messages belonging to that conversation, ordered by most recent first.

ConversationFragment


    @Override
    public void onViewCreated(
        @NonNull View view,
        @Nullable Bundle savedInstanceState
    ) {
        LifecycleOwner lifecycleOwner = getViewLifecycleOwner();
        viewModel = new ViewModelProvider(this).get(ConversationViewModel.class);
        viewModel
            .getMessageList(lifecycleOwner, conversationId) // conversationId is a global variable
            .observe(lifecycleOwner, messagePagingData -> adapter.submitData(
                lifecycleOwner.getLifecycle(),
                messagePagingData 
            ));

        super.onViewCreated(view, savedInstanceState);

    }

ConversationViewModel


    final PagingConfig pagingConfig = new PagingConfig(10, 10, false, 20);
    private final ConversationRepository conversationRepository;

    public ConversationViewModel(@NonNull Application application) {
        super(application);
        conversationRepository = new ConversationRepository(application);
    }

    public LiveData<PagingData<ItemMessage>> getMessageList(
        @NonNull LifecycleOwner lifecycleOwner,
        @NonNull String conversationId
    ) {
        return PagingLiveData.cachedIn(
            PagingLiveData.getLiveData(new Pager<>(pagingConfig, () -> conversationRepository.getMessageList(conversationId))),
            lifecycleOwner.getLifecycle()
        );
    }

ConversationRepository

    private final MessageDao messageDao;

    public ConversationRepository(@NonNull Context context) {
        AppDatabase database = AppDatabase.getDatabase(context);
        messageDao = database.messageDao();
    }

    public PagingSource<Integer, ItemMessage> getMessageList(@NonNull String conversationId) {
        return messageDao.getMessageList(conversationId);
    }

MessageDao

    @Query(
        "SELECT * FROM Message " +
        "WHERE Message.conversationId = :conversationId " +
        "ORDER BY Message.time DESC"
    )
    public abstract PagingSource<Integer, ItemMessage> getMessageList(String conversationId);

Now my goal is to be able to open the conversation already scrolled at a specific message.

I also do not want to load the entire conversation and then scroll to the message, some conversations can be very long and I do not want to put the user on an auto scroll that can take ages to reach the specific message.

Ideally the way I envision this being done correct is to pass the message id to be in view, load a chunk of X messages surrounding before and after that message id and then after it is already presented to the user in the RecyclerView it will load more if the user goes up or down.

This is not meant to use network requests, the entire conversation is available in the database already so it will only use the information that is already in the database.

I've tried understanding the examples that use ItemKeyedDataSource or PageKeyedDataSource, but I cannot go anywhere because every single time those examples are in Kotlin only and require Retrofit to work, which I do not use. As it is these examples are completely useless for anyone like me that is in Java and not using Retrofit.

How can this be achieved?

Please provide an answer in Java, not just Kotlin only (kotlin is OK as long as it's in java as well) and please do not suggest new libraries.

Upvotes: 6

Views: 3756

Answers (1)

Shadow
Shadow

Reputation: 4782

As far as I could find the official documentation does not provide any sort of clue on how to solve this one for a Paging + Room integration. In fact, it doesn't provide any solution whatsoever to scroll to an item in a PagingDataAdapter, period.

The only thing that worked for me so far was to run two queries every single time I wish to accomplish this: one to find the item position in the result query list and the other to actually load said list with the initialKey set in the Pager constructor with the value of the item position we queried previously.

And if you're feeling a bit confused, this does not end here, because even the explanation for what is initialKey and how to use it is just not documented. No, seriously: What does the initialKey parameter do in the Pager constructor

So there's two guessing games here: one to find a proper way to lookup the item index from a result list and another to set it up properly in the final query.

I hope the Paging 3 documentation gets improved soon to cover these very basic issues.

In the end this is an example of how I managed to get this problem kind of working for me, even though I have no idea if this is the proper way to do it because, again, their documentation is absolutely lacking in this department.

  1. Create two identical queries for the list results you desire
  2. One of those queries only returns a full list of the results based on a key you'll use to uniquely identify an item. In my case it is messageId.
  3. Load the query in 2 and individually iterate the results list using a for... loop until you find the item you want to know its position in the list. That position is given by the iterator you use in your loop block.
  4. Pass the item position from 3 as initialKey parameter into your Pager builder of the final query
  5. The first chunk of data you'll receive now will contain the item you want
  6. If you want you can now scroll to that item in your RecyclerView, but you'll have to query it from the current list of items loaded in the adapter. See about using the .snapshot() in the PagingAdapter

That's it, now I can finally load an item at a certain position using Paging 3 + Room, with absolutely no idea of whether this is the proper way to do it thanks to the completely absent documentation for this.

Upvotes: 3

Related Questions