/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.paging; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.arch.core.executor.ArchTaskExecutor; import androidx.lifecycle.LiveData; import androidx.recyclerview.widget.AdapterListUpdateCallback; import androidx.recyclerview.widget.AsyncDifferConfig; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListUpdateCallback; import androidx.recyclerview.widget.RecyclerView; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; /** * Helper object for mapping a {@link PagedList} into a * {@link androidx.recyclerview.widget.RecyclerView.Adapter RecyclerView.Adapter}. *
* For simplicity, the {@link PagedListAdapter} wrapper class can often be used instead of the * differ directly. This diff class is exposed for complex cases, and where overriding an adapter * base class to support paging isn't convenient. *
* When consuming a {@link LiveData} of PagedList, you can observe updates and dispatch them * directly to {@link #submitList(PagedList)}. The AsyncPagedListDiffer then can present this * updating data set simply for an adapter. It listens to PagedList loading callbacks, and uses * DiffUtil on a background thread to compute updates as new PagedLists are received. *
* It provides a simple list-like API with {@link #getItem(int)} and {@link #getItemCount()} for an * adapter to acquire and present data objects. *
* A complete usage pattern with Room would look like this: *
* {@literal @}Dao
* interface UserDao {
* {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
* public abstract DataSource.Factory<Integer, User> usersByLastName();
* }
*
* class MyViewModel extends ViewModel {
* public final LiveData<PagedList<User>> usersList;
* public MyViewModel(UserDao userDao) {
* usersList = new LivePagedListBuilder<>(
* userDao.usersByLastName(), /* page size {@literal *}/ 20).build();
* }
* }
*
* class MyActivity extends AppCompatActivity {
* {@literal @}Override
* public void onCreate(Bundle savedState) {
* super.onCreate(savedState);
* MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
* RecyclerView recyclerView = findViewById(R.id.user_list);
* final UserAdapter adapter = new UserAdapter();
* viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList));
* recyclerView.setAdapter(adapter);
* }
* }
*
* class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
* private final AsyncPagedListDiffer<User> mDiffer
* = new AsyncPagedListDiffer(this, DIFF_CALLBACK);
* {@literal @}Override
* public int getItemCount() {
* return mDiffer.getItemCount();
* }
* public void submitList(PagedList<User> pagedList) {
* mDiffer.submitList(pagedList);
* }
* {@literal @}Override
* public void onBindViewHolder(UserViewHolder holder, int position) {
* User user = mDiffer.getItem(position);
* if (user != null) {
* holder.bindTo(user);
* } else {
* // Null defines a placeholder item - AsyncPagedListDiffer will automatically
* // invalidate this row when the actual object is loaded from the database
* holder.clear();
* }
* }
* public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK =
* new DiffUtil.ItemCallback<User>() {
* {@literal @}Override
* public boolean areItemsTheSame(
* {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
* // User properties may have changed if reloaded from the DB, but ID is fixed
* return oldUser.getId() == newUser.getId();
* }
* {@literal @}Override
* public boolean areContentsTheSame(
* {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) {
* // NOTE: if you use equals, your object must properly override Object#equals()
* // Incorrectly returning false here will result in too many animations.
* return oldUser.equals(newUser);
* }
* }
* }
*
* @param * Note that this operates on both loaded items and null padding within the PagedList. * * @param index Index of item to get, must be >= 0, and < {@link #getItemCount()}. * @return The item, or null, if a null placeholder is at the specified position. */ @SuppressWarnings("WeakerAccess") @Nullable public T getItem(int index) { if (mPagedList == null) { if (mSnapshot == null) { throw new IndexOutOfBoundsException( "Item count is zero, getItem() call is invalid"); } else { return mSnapshot.get(index); } } mPagedList.loadAround(index); return mPagedList.get(index); } /** * Get the number of items currently presented by this Differ. This value can be directly * returned to {@link RecyclerView.Adapter#getItemCount()}. * * @return Number of items being presented. */ @SuppressWarnings("WeakerAccess") public int getItemCount() { if (mPagedList != null) { return mPagedList.size(); } return mSnapshot == null ? 0 : mSnapshot.size(); } /** * Pass a new PagedList to the differ. *
* If a PagedList is already present, a diff will be computed asynchronously on a background
* thread. When the diff is computed, it will be applied (dispatched to the
* {@link ListUpdateCallback}), and the new PagedList will be swapped in as the
* {@link #getCurrentList() current list}.
*
* @param pagedList The new PagedList.
*/
public void submitList(@Nullable final PagedList
* If a PagedList is already present, a diff will be computed asynchronously on a background
* thread. When the diff is computed, it will be applied (dispatched to the
* {@link ListUpdateCallback}), and the new PagedList will be swapped in as the
* {@link #getCurrentList() current list}.
*
* The commit callback can be used to know when the PagedList is committed, but note that it
* may not be executed. If PagedList B is submitted immediately after PagedList A, and is
* committed directly, the callback associated with PagedList A will not be run.
*
* @param pagedList The new PagedList.
* @param commitCallback Optional runnable that is executed when the PagedList is committed, if
* it is committed.
*/
@SuppressWarnings("ReferenceEquality")
public void submitList(@Nullable final PagedList
* This is not necessarily the most recent list passed to {@link #submitList(PagedList)},
* because a diff is computed asynchronously between the new list and the current list before
* updating the currentList value. May be null if no PagedList is being presented.
*
* @return The list currently being displayed, may be null.
*/
@SuppressWarnings("WeakerAccess")
@Nullable
public PagedList