| /* |
| * 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.recyclerview.widget.AdapterListUpdateCallback; |
| import androidx.recyclerview.widget.AsyncDifferConfig; |
| import androidx.recyclerview.widget.DiffUtil; |
| import androidx.recyclerview.widget.RecyclerView; |
| |
| /** |
| * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting paged data from |
| * {@link PagedList}s in a {@link RecyclerView}. |
| * <p> |
| * This class is a convenience wrapper around {@link AsyncPagedListDiffer} that implements common |
| * default behavior for item counting, and listening to PagedList update callbacks. |
| * <p> |
| * While using a LiveData<PagedList> is an easy way to provide data to the adapter, it isn't |
| * required - you can use {@link #submitList(PagedList)} when new lists are available. |
| * <p> |
| * PagedListAdapter listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on |
| * a background thread to compute fine grained updates as new PagedLists are received. |
| * <p> |
| * Handles both the internal paging of the list as more data is loaded, and updates in the form of |
| * new PagedLists. |
| * <p> |
| * A complete usage pattern with Room would look like this: |
| * <pre> |
| * {@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); |
| * UserAdapter<User> adapter = new UserAdapter(); |
| * viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList)); |
| * recyclerView.setAdapter(adapter); |
| * } |
| * } |
| * |
| * class UserAdapter extends PagedListAdapter<User, UserViewHolder> { |
| * public UserAdapter() { |
| * super(DIFF_CALLBACK); |
| * } |
| * {@literal @}Override |
| * public void onBindViewHolder(UserViewHolder holder, int position) { |
| * User user = getItem(position); |
| * if (user != null) { |
| * holder.bindTo(user); |
| * } else { |
| * // Null defines a placeholder item - PagedListAdapter 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); |
| * } |
| * } |
| * }</pre> |
| * |
| * Advanced users that wish for more control over adapter behavior, or to provide a specific base |
| * class should refer to {@link AsyncPagedListDiffer}, which provides the mapping from paging |
| * events to adapter-friendly callbacks. |
| * |
| * @param <T> Type of the PagedLists this Adapter will receive. |
| * @param <VH> A class that extends ViewHolder that will be used by the adapter. |
| */ |
| public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder> |
| extends RecyclerView.Adapter<VH> { |
| final AsyncPagedListDiffer<T> mDiffer; |
| private final AsyncPagedListDiffer.PagedListListener<T> mListener = |
| new AsyncPagedListDiffer.PagedListListener<T>() { |
| @Override |
| public void onCurrentListChanged( |
| @Nullable PagedList<T> previousList, @Nullable PagedList<T> currentList) { |
| PagedListAdapter.this.onCurrentListChanged(currentList); |
| PagedListAdapter.this.onCurrentListChanged(previousList, currentList); |
| } |
| }; |
| private final PagedList.LoadStateListener mLoadStateListener = |
| new PagedList.LoadStateListener() { |
| @Override |
| public void onLoadStateChanged(@NonNull PagedList.LoadType type, |
| @NonNull PagedList.LoadState state, @Nullable Throwable error) { |
| PagedListAdapter.this.onLoadStateChanged(type, state, error); |
| } |
| }; |
| |
| /** |
| * Creates a PagedListAdapter with default threading and |
| * {@link androidx.recyclerview.widget.ListUpdateCallback}. |
| * |
| * Convenience for {@link #PagedListAdapter(AsyncDifferConfig)}, which uses default threading |
| * behavior. |
| * |
| * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to |
| * compare items in the list. |
| */ |
| protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) { |
| mDiffer = new AsyncPagedListDiffer<>(this, diffCallback); |
| mDiffer.addPagedListListener(mListener); |
| mDiffer.addLoadStateListener(mLoadStateListener); |
| } |
| |
| protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) { |
| mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config); |
| mDiffer.addPagedListListener(mListener); |
| mDiffer.addLoadStateListener(mLoadStateListener); |
| } |
| |
| /** |
| * Set the new list to be displayed. |
| * <p> |
| * If a list is already being displayed, a diff will be computed on a background thread, which |
| * will dispatch Adapter.notifyItem events on the main thread. |
| * |
| * @param pagedList The new list to be displayed. |
| */ |
| public void submitList(@Nullable PagedList<T> pagedList) { |
| mDiffer.submitList(pagedList); |
| } |
| |
| /** |
| * Set the new list to be displayed. |
| * <p> |
| * If a list is already being displayed, a diff will be computed on a background thread, which |
| * will dispatch Adapter.notifyItem events on the main thread. |
| * <p> |
| * 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 list to be displayed. |
| * @param commitCallback Optional runnable that is executed when the PagedList is committed, if |
| * it is committed. |
| */ |
| public void submitList(@Nullable PagedList<T> pagedList, |
| @Nullable final Runnable commitCallback) { |
| mDiffer.submitList(pagedList, commitCallback); |
| } |
| |
| @Nullable |
| protected T getItem(int position) { |
| return mDiffer.getItem(position); |
| } |
| |
| @Override |
| public int getItemCount() { |
| return mDiffer.getItemCount(); |
| } |
| |
| /** |
| * Returns the PagedList currently being displayed by the Adapter. |
| * <p> |
| * 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. |
| * |
| * @see #onCurrentListChanged(PagedList, PagedList) |
| */ |
| @Nullable |
| public PagedList<T> getCurrentList() { |
| return mDiffer.getCurrentList(); |
| } |
| |
| /** |
| * Called when the current PagedList is updated. |
| * <p> |
| * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't |
| * needed (such as when the first list is passed, or the list is cleared). In either case, |
| * PagedListAdapter will simply call |
| * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}. |
| * <p> |
| * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList |
| * to a snapshot version of the PagedList during a diff. This means you cannot observe each |
| * PagedList via this method. |
| * |
| * @deprecated Use the two argument variant instead: |
| * {@link #onCurrentListChanged(PagedList, PagedList)} |
| * |
| * @param currentList new PagedList being displayed, may be null. |
| * |
| * @see #getCurrentList() |
| */ |
| @SuppressWarnings("DeprecatedIsStillUsed") |
| @Deprecated |
| public void onCurrentListChanged(@Nullable PagedList<T> currentList) { |
| } |
| |
| /** |
| * Called when the current PagedList is updated. |
| * <p> |
| * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't |
| * needed (such as when the first list is passed, or the list is cleared). In either case, |
| * PagedListAdapter will simply call |
| * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}. |
| * <p> |
| * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList |
| * to a snapshot version of the PagedList during a diff. This means you cannot observe each |
| * PagedList via this method. |
| * |
| * @param previousList PagedList that was previously displayed, may be null. |
| * @param currentList new PagedList being displayed, may be null. |
| * |
| * @see #getCurrentList() |
| */ |
| public void onCurrentListChanged( |
| @Nullable PagedList<T> previousList, @Nullable PagedList<T> currentList) { |
| } |
| |
| |
| /** |
| * Called when the LoadState for a particular type of load (START, END, REFRESH) has |
| * changed. |
| * <p> |
| * REFRESH events can be used to drive a {@code SwipeRefreshLayout}, or START/END events |
| * can be used to drive loading spinner items in the Adapter. |
| * |
| * @param type Type of load - START, END, or REFRESH. |
| * @param state State of load - IDLE, LOADING, DONE, ERROR, or RETRYABLE_ERROR |
| * @param error Error, if in an error state, null otherwise. |
| */ |
| public void onLoadStateChanged(@NonNull PagedList.LoadType type, |
| @NonNull PagedList.LoadState state, @Nullable Throwable error) { |
| } |
| |
| /** |
| * Add a LoadStateListener to observe the loading state of the current PagedList. |
| * |
| * As new PagedLists are submitted and displayed, the listener will be notified to reflect |
| * current REFRESH, START, and END states. |
| * |
| * @param listener Listener to receive updates. |
| * |
| * @see #removeLoadStateListener(PagedList.LoadStateListener) |
| */ |
| public void addLoadStateListener(PagedList.LoadStateListener listener) { |
| mDiffer.addLoadStateListener(listener); |
| } |
| |
| /** |
| * Remove a previously registered LoadStateListener. |
| * |
| * @param listener Previously registered listener. |
| * @see #addLoadStateListener(PagedList.LoadStateListener) |
| */ |
| public void removeLoadStateListener(PagedList.LoadStateListener listener) { |
| mDiffer.removeLoadStateListListener(listener); |
| } |
| } |