124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik/* 224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * Copyright (C) 2017 The Android Open Source Project 324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * 424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * Licensed under the Apache License, Version 2.0 (the "License"); 524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * you may not use this file except in compliance with the License. 624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * You may obtain a copy of the License at 724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * 824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * http://www.apache.org/licenses/LICENSE-2.0 924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * 1024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * Unless required by applicable law or agreed to in writing, software 1124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * distributed under the License is distributed on an "AS IS" BASIS, 1224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * See the License for the specific language governing permissions and 1424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * limitations under the License. 1524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik */ 1624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik 17bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverettepackage androidx.paging; 1824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik 19bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.NonNull; 20bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.Nullable; 21bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.recyclerview.widget.AdapterListUpdateCallback; 22bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.recyclerview.widget.AsyncDifferConfig; 23bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.recyclerview.widget.DiffUtil; 24bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.recyclerview.widget.RecyclerView; 2524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik 2624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik/** 2724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * {@link RecyclerView.Adapter RecyclerView.Adapter} base class for presenting paged data from 2824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * {@link PagedList}s in a {@link RecyclerView}. 2924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * <p> 304b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik * This class is a convenience wrapper around {@link AsyncPagedListDiffer} that implements common 314b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik * default behavior for item counting, and listening to PagedList update callbacks. 3224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * <p> 339adfe48e0670127286c20420be729aa9eab204dcChris Craik * While using a LiveData<PagedList> is an easy way to provide data to the adapter, it isn't 3417347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * required - you can use {@link #submitList(PagedList)} when new lists are available. 35114bdca94b6571ef9f45ea6e826715141741d49eChris Craik * <p> 36114bdca94b6571ef9f45ea6e826715141741d49eChris Craik * PagedListAdapter listens to PagedList loading callbacks as pages are loaded, and uses DiffUtil on 37114bdca94b6571ef9f45ea6e826715141741d49eChris Craik * a background thread to compute fine grained updates as new PagedLists are received. 389adfe48e0670127286c20420be729aa9eab204dcChris Craik * <p> 3924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * Handles both the internal paging of the list as more data is loaded, and updates in the form of 4024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * new PagedLists. 4124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * <p> 4224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * A complete usage pattern with Room would look like this: 4324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * <pre> 4424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * {@literal @}Dao 4524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * interface UserDao { 4624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC") 475dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik * public abstract DataSource.Factory<Integer, User> usersByLastName(); 4824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * } 4924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * 5024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * class MyViewModel extends ViewModel { 5124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * public final LiveData<PagedList<User>> usersList; 5224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * public MyViewModel(UserDao userDao) { 53895f27a719be103130ac2ba04ffedbeb600ef027Chris Craik * usersList = new LivePagedListBuilder<>( 545dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik * userDao.usersByLastName(), /* page size {@literal *}/ 20).build(); 5524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * } 5624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * } 5724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * 580a8fd09f1ad194f5e644892c04b78a6fa8026335Chris Craik * class MyActivity extends AppCompatActivity { 5924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * {@literal @}Override 6024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * public void onCreate(Bundle savedState) { 6124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * super.onCreate(savedState); 6224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class); 6324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * RecyclerView recyclerView = findViewById(R.id.user_list); 649adfe48e0670127286c20420be729aa9eab204dcChris Craik * UserAdapter<User> adapter = new UserAdapter(); 6517347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * viewModel.usersList.observe(this, pagedList -> adapter.submitList(pagedList)); 6624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * recyclerView.setAdapter(adapter); 6724418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * } 6824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * } 6924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * 709adfe48e0670127286c20420be729aa9eab204dcChris Craik * class UserAdapter extends PagedListAdapter<User, UserViewHolder> { 719adfe48e0670127286c20420be729aa9eab204dcChris Craik * public UserAdapter() { 723fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * super(DIFF_CALLBACK); 7324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * } 7424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * {@literal @}Override 759adfe48e0670127286c20420be729aa9eab204dcChris Craik * public void onBindViewHolder(UserViewHolder holder, int position) { 769adfe48e0670127286c20420be729aa9eab204dcChris Craik * User user = getItem(position); 779adfe48e0670127286c20420be729aa9eab204dcChris Craik * if (user != null) { 7824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * holder.bindTo(user); 799adfe48e0670127286c20420be729aa9eab204dcChris Craik * } else { 809adfe48e0670127286c20420be729aa9eab204dcChris Craik * // Null defines a placeholder item - PagedListAdapter will automatically invalidate 819adfe48e0670127286c20420be729aa9eab204dcChris Craik * // this row when the actual object is loaded from the database 829adfe48e0670127286c20420be729aa9eab204dcChris Craik * holder.clear(); 8324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * } 8424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * } 854b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik * public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = 864b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik * new DiffUtil.ItemCallback<User>() { 873fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * {@literal @}Override 883fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * public boolean areItemsTheSame( 893fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { 903fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * // User properties may have changed if reloaded from the DB, but ID is fixed 913fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * return oldUser.getId() == newUser.getId(); 923fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * } 933fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * {@literal @}Override 943fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * public boolean areContentsTheSame( 953fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * {@literal @}NonNull User oldUser, {@literal @}NonNull User newUser) { 963fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * // NOTE: if you use equals, your object must properly override Object#equals() 973fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * // Incorrectly returning false here will result in too many animations. 983fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * return oldUser.equals(newUser); 993fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * } 1003fcc20f47901623f0f23c74bd71ef24a3f484618Chris Craik * } 1019adfe48e0670127286c20420be729aa9eab204dcChris Craik * }</pre> 10224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * 10324418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * Advanced users that wish for more control over adapter behavior, or to provide a specific base 10417347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * class should refer to {@link AsyncPagedListDiffer}, which provides the mapping from paging 10524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * events to adapter-friendly callbacks. 10624418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * 1074b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik * @param <T> Type of the PagedLists this Adapter will receive. 10824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * @paramA class that extends ViewHolder that will be used by the adapter. 10924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik */ 11024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craikpublic abstract class PagedListAdapter<T, VH extends RecyclerView.ViewHolder> 111f0d13608aae3b4700d84c1c4532abbea56ea7a28Chris Craik extends RecyclerView.Adapter<VH> { 1124b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik private final AsyncPagedListDiffer<T> mDiffer; 11317347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik private final AsyncPagedListDiffer.PagedListListener<T> mListener = 11417347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik new AsyncPagedListDiffer.PagedListListener<T>() { 11567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik @Override 11667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik public void onCurrentListChanged(@Nullable PagedList<T> currentList) { 11767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik PagedListAdapter.this.onCurrentListChanged(currentList); 11867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik } 11967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik }; 12024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik 1219adfe48e0670127286c20420be729aa9eab204dcChris Craik /** 1229adfe48e0670127286c20420be729aa9eab204dcChris Craik * Creates a PagedListAdapter with default threading and 123bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverette * {@link androidx.recyclerview.widget.ListUpdateCallback}. 1249adfe48e0670127286c20420be729aa9eab204dcChris Craik * 12517347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * Convenience for {@link #PagedListAdapter(AsyncDifferConfig)}, which uses default threading 1269adfe48e0670127286c20420be729aa9eab204dcChris Craik * behavior. 1279adfe48e0670127286c20420be729aa9eab204dcChris Craik * 12817347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * @param diffCallback The {@link DiffUtil.ItemCallback DiffUtil.ItemCallback} instance to 12917347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * compare items in the list. 1309adfe48e0670127286c20420be729aa9eab204dcChris Craik */ 13117347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) { 1324b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik mDiffer = new AsyncPagedListDiffer<>(this, diffCallback); 1334b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik mDiffer.mListener = mListener; 13424418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik } 13524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik 1369adfe48e0670127286c20420be729aa9eab204dcChris Craik @SuppressWarnings("unused, WeakerAccess") 13717347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) { 1384b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config); 1394b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik mDiffer.mListener = mListener; 14024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik } 14124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik 14224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik /** 1439adfe48e0670127286c20420be729aa9eab204dcChris Craik * Set the new list to be displayed. 1449adfe48e0670127286c20420be729aa9eab204dcChris Craik * <p> 1459adfe48e0670127286c20420be729aa9eab204dcChris Craik * If a list is already being displayed, a diff will be computed on a background thread, which 1469adfe48e0670127286c20420be729aa9eab204dcChris Craik * will dispatch Adapter.notifyItem events on the main thread. 1479adfe48e0670127286c20420be729aa9eab204dcChris Craik * 1489adfe48e0670127286c20420be729aa9eab204dcChris Craik * @param pagedList The new list to be displayed. 14924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik */ 15017347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik public void submitList(PagedList<T> pagedList) { 1514b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik mDiffer.submitList(pagedList); 1529adfe48e0670127286c20420be729aa9eab204dcChris Craik } 1539adfe48e0670127286c20420be729aa9eab204dcChris Craik 1549adfe48e0670127286c20420be729aa9eab204dcChris Craik @Nullable 1559adfe48e0670127286c20420be729aa9eab204dcChris Craik protected T getItem(int position) { 1564b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik return mDiffer.getItem(position); 1579adfe48e0670127286c20420be729aa9eab204dcChris Craik } 15824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik 15924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik @Override 16024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik public int getItemCount() { 1614b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik return mDiffer.getItemCount(); 16224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik } 163fd4fa4a65be59806d14e4625397948da008506b4Chris Craik 164fd4fa4a65be59806d14e4625397948da008506b4Chris Craik /** 1654b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik * Returns the PagedList currently being displayed by the Adapter. 166fd4fa4a65be59806d14e4625397948da008506b4Chris Craik * <p> 16717347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * This is not necessarily the most recent list passed to {@link #submitList(PagedList)}, 16817347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * because a diff is computed asynchronously between the new list and the current list before 1694b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik * updating the currentList value. May be null if no PagedList is being presented. 170fd4fa4a65be59806d14e4625397948da008506b4Chris Craik * 171fd4fa4a65be59806d14e4625397948da008506b4Chris Craik * @return The list currently being displayed. 172fd4fa4a65be59806d14e4625397948da008506b4Chris Craik */ 173fd4fa4a65be59806d14e4625397948da008506b4Chris Craik @Nullable 174fd4fa4a65be59806d14e4625397948da008506b4Chris Craik public PagedList<T> getCurrentList() { 1754b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik return mDiffer.getCurrentList(); 176fd4fa4a65be59806d14e4625397948da008506b4Chris Craik } 17767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik 17867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik /** 17967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * Called when the current PagedList is updated. 18067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * <p> 18117347a161ae0b69ceef26992d5ec3d136d9ae47dChris Craik * This may be dispatched as part of {@link #submitList(PagedList)} if a background diff isn't 18267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * needed (such as when the first list is passed, or the list is cleared). In either case, 18367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * PagedListAdapter will simply call 18467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * {@link #notifyItemRangeInserted(int, int) notifyItemRangeInserted/Removed(0, mPreviousSize)}. 18567077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * <p> 18667077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * This method will <em>not</em>be called when the Adapter switches from presenting a PagedList 18767077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * to a snapshot version of the PagedList during a diff. This means you cannot observe each 18867077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * PagedList via this method. 18967077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * 19067077406223e49eba5ecd0def10ca80dd6909f16Chris Craik * @param currentList new PagedList being displayed, may be null. 19167077406223e49eba5ecd0def10ca80dd6909f16Chris Craik */ 19267077406223e49eba5ecd0def10ca80dd6909f16Chris Craik @SuppressWarnings("WeakerAccess") 19367077406223e49eba5ecd0def10ca80dd6909f16Chris Craik public void onCurrentListChanged(@Nullable PagedList<T> currentList) { 19467077406223e49eba5ecd0def10ca80dd6909f16Chris Craik } 19524418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik} 196