1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package androidx.paging; 18 19import androidx.annotation.NonNull; 20import androidx.annotation.Nullable; 21import androidx.recyclerview.widget.AdapterListUpdateCallback; 22import androidx.recyclerview.widget.AsyncDifferConfig; 23import androidx.recyclerview.widget.DiffUtil; 24import androidx.recyclerview.widget.RecyclerView; 25 26/** 27 * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting paged data from 28 * {@link PagedList}s in a {@link RecyclerView}. 29 * <p> 30 * This class is a convenience wrapper around {@link AsyncPagedListDiffer} that implements common 31 * default behavior for item counting, and listening to PagedList update callbacks. 32 * <p> 33 * While using a LiveData<PagedList> is an easy way to provide data to the adapter, it isn't 34 * required - you can use {@link #submitList(PagedList)} when new lists are available. 35 * <p> 36 * PagedListAdapter listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on 37 * a background thread to compute fine grained updates as new PagedLists are received. 38 * <p> 39 * Handles both the internal paging of the list as more data is loaded, and updates in the form of 40 * new PagedLists. 41 * <p> 42 * A complete usage pattern with Room would look like this: 43 * <pre> 44 * {@literal @}Dao 45 * interface UserDao { 46 * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") 47 * public abstract DataSource.Factory<Integer, User> usersByLastName(); 48 * } 49 * 50 * class MyViewModel extends ViewModel { 51 * public final LiveData<PagedList<User>> usersList; 52 * public MyViewModel(UserDao userDao) { 53 * usersList = new LivePagedListBuilder<>( 54 * userDao.usersByLastName(), /* page size {@literal *}/ 20).build(); 55 * } 56 * } 57 * 58 * class MyActivity extends AppCompatActivity { 59 * {@literal @}Override 60 * public void onCreate(Bundle savedState) { 61 * super.onCreate(savedState); 62 * MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class); 63 * RecyclerView recyclerView = findViewById(R.id.user_list); 64 * UserAdapter<User> adapter = new UserAdapter(); 65 * viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList)); 66 * recyclerView.setAdapter(adapter); 67 * } 68 * } 69 * 70 * class UserAdapter extends PagedListAdapter<User, UserViewHolder> { 71 * public UserAdapter() { 72 * super(DIFF_CALLBACK); 73 * } 74 * {@literal @}Override 75 * public void onBindViewHolder(UserViewHolder holder, int position) { 76 * User user = getItem(position); 77 * if (user != null) { 78 * holder.bindTo(user); 79 * } else { 80 * // Null defines a placeholder item - PagedListAdapter will automatically invalidate 81 * // this row when the actual object is loaded from the database 82 * holder.clear(); 83 * } 84 * } 85 * public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = 86 * new DiffUtil.ItemCallback<User>() { 87 * {@literal @}Override 88 * public boolean areItemsTheSame( 89 * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { 90 * // User properties may have changed if reloaded from the DB, but ID is fixed 91 * return oldUser.getId() == newUser.getId(); 92 * } 93 * {@literal @}Override 94 * public boolean areContentsTheSame( 95 * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { 96 * // NOTE: if you use equals, your object must properly override Object#equals() 97 * // Incorrectly returning false here will result in too many animations. 98 * return oldUser.equals(newUser); 99 * } 100 * } 101 * }</pre> 102 * 103 * Advanced users that wish for more control over adapter behavior, or to provide a specific base 104 * class should refer to {@link AsyncPagedListDiffer}, which provides the mapping from paging 105 * events to adapter-friendly callbacks. 106 * 107 * @param <T> Type of the PagedLists this Adapter will receive. 108 * @paramA class that extends ViewHolder that will be used by the adapter. 109 */ 110public abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder> 111 extends RecyclerView.Adapter<VH> { 112 private final AsyncPagedListDiffer<T> mDiffer; 113 private final AsyncPagedListDiffer.PagedListListener<T> mListener = 114 new AsyncPagedListDiffer.PagedListListener<T>() { 115 @Override 116 public void onCurrentListChanged(@Nullable PagedList<T> currentList) { 117 PagedListAdapter.this.onCurrentListChanged(currentList); 118 } 119 }; 120 121 /** 122 * Creates a PagedListAdapter with default threading and 123 * {@link androidx.recyclerview.widget.ListUpdateCallback}. 124 * 125 * Convenience for {@link #PagedListAdapter(AsyncDifferConfig)}, which uses default threading 126 * behavior. 127 * 128 * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to 129 * compare items in the list. 130 */ 131 protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) { 132 mDiffer = new AsyncPagedListDiffer<>(this, diffCallback); 133 mDiffer.mListener = mListener; 134 } 135 136 @SuppressWarnings("unused, WeakerAccess") 137 protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) { 138 mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config); 139 mDiffer.mListener = mListener; 140 } 141 142 /** 143 * Set the new list to be displayed. 144 * <p> 145 * If a list is already being displayed, a diff will be computed on a background thread, which 146 * will dispatch Adapter.notifyItem events on the main thread. 147 * 148 * @param pagedList The new list to be displayed. 149 */ 150 public void submitList(PagedList<T> pagedList) { 151 mDiffer.submitList(pagedList); 152 } 153 154 @Nullable 155 protected T getItem(int position) { 156 return mDiffer.getItem(position); 157 } 158 159 @Override 160 public int getItemCount() { 161 return mDiffer.getItemCount(); 162 } 163 164 /** 165 * Returns the PagedList currently being displayed by the Adapter. 166 * <p> 167 * This is not necessarily the most recent list passed to {@link #submitList(PagedList)}, 168 * because a diff is computed asynchronously between the new list and the current list before 169 * updating the currentList value. May be null if no PagedList is being presented. 170 * 171 * @return The list currently being displayed. 172 */ 173 @Nullable 174 public PagedList<T> getCurrentList() { 175 return mDiffer.getCurrentList(); 176 } 177 178 /** 179 * Called when the current PagedList is updated. 180 * <p> 181 * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't 182 * needed (such as when the first list is passed, or the list is cleared). In either case, 183 * PagedListAdapter will simply call 184 * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}. 185 * <p> 186 * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList 187 * to a snapshot version of the PagedList during a diff. This means you cannot observe each 188 * PagedList via this method. 189 * 190 * @param currentList new PagedList being displayed, may be null. 191 */ 192 @SuppressWarnings("WeakerAccess") 193 public void onCurrentListChanged(@Nullable PagedList<T> currentList) { 194 } 195} 196