I have a simple setup of 2 fragments: ConversationFragment
and DetailsFragment
I am using Room
with Paging 3 library
and to populate the ConversationFragment
I am using a PagingLiveData
implementation together with a AndroidViewModel
belonging to the ConversationFragment
.
I am not using the Navigation Components
here, just a common fragment navigation as per Android documentation.
From that fragment I can open the DetailsFragment
and then return back to the fragment again. Everything is working well, until I open said fragment and return, then the observer that was tied in the ConversationFragment
is lost since that fragment is being destroyed when opening the DetailsFragment
.
So far this is not a big issue, I can restart the observer again and it does work when I do that.
However, when I attach the observer again the entire list reflows, this causes the items in the RecyclerView
to go wild, the position the list was on is lost and the scrollbar changes sizes which confirms pages are being loaded/reloaded.
I could withstand the weird behavior to a degree, but to have the position lost on top of that is not acceptable.
I looked into caching the results in the view model, but the examples I could find in the available documentation are basic and do not show how the same could be achieved using a LiveData<PagingData<...>
object.
Currently this is what I have:
ConversationFragment
@Override
public void onViewCreated(
@NonNull View view,
@Nullable Bundle savedInstanceState
) {
if (viewModel == null) {
viewModel = new ViewModelProvider(this).get(ConversationViewModel.class);
}
if (savedInstanceState == null) {
// adapter is initialized in onCreateView
viewModel
.getList(getViewLifecycleOwner())
.observe(getViewLifecycleOwner(), pagingData -> adapter.submitData(lifecycleOwner.getLifecycle(), pagingData));
}
super.onViewCreated(view, savedInstanceState);
}
ConversationViewModel
public class ConversationViewModel extends AndroidViewModel {
final PagingConfig pagingConfig = new PagingConfig(10, 10, false, 20);
private final Repository repository;
private final MutableLiveData<PagingData<ItemView>> messageList;
public ConversationFragmentVM(@NonNull Application application) {
super(application);
messageList = new MutableLiveData<>();
repository = new Repository(application);
}
public LiveData<PagingData<ItemView>> getList(@NonNull LifecycleOwner lifecycleOwner) {
// at first I tried only setting the value if it was null
// but since the observer is lost on destroy and the value
// is not it would never be able to "restart" the observer
// again
// if (messageList.getValue() == null) {
PagingLiveData.cachedIn(
PagingLiveData.getLiveData(new Pager<>(pagingConfig, () -> repository.getMessageList())),
lifecycleOwner.getLifecycle()
).observe(lifecycleOwner, messageList::setValue);
// }
return messageList;
}
}
As it is, even if I return the result of the PagingLiveData.cachedIn
the behavior is the same when I return to the fragment; the items show an erratic behavior in the recyclerview list and the position it was on is totally lost.
This is what I was trying to achieve to see if it fixed my issue:
This is a code lab available here: https://developer.android.com/codelabs/android-training-livedata-viewmodel#8
As you can see the mAllWords
are cached and they are only initialized when the view model is constructed for the first time, any subsequent changes are simply updates and would only require new observers to be attached when the fragment is destroyed and created again while still in the back stack.
This is what I was trying to do, but it does not work the way I thought it did, at least it is not as straight forward as I thought.
How can this be achieved?
There's quite a lot to unpack here but my best guess would be your
getList
method inConversationViewModel
. You're on the right track with using ViewModels and LiveData to persist data across navigation but here you're recreating the LiveData every time this method is called, meaning when you resumeConversationFragment
andonViewCreated
is called, it creates a new Pager which fetches new data.The solution would be to create the pager when
ConversationViewModel
is first created and then accessing the LiveData object itself, rather than the method. You can see this in the Codelab example, they assign the LiveData in the constructor and simply return the already created LiveData in thegetAllWords()
method.I'm using this as an example, change
ConversationViewModel
to something like this and change it to use your config and repository.Then in your fragment, you simply observe getList() like usual, except this time it's returning a prexisting version.
I haven't been able to test that this compiles or works so let me know if it doesn't and I'll update this answer.