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&lt;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&lt;Integer, User> usersByLastName();
4824418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * }
4924418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik *
5024418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik * class MyViewModel extends ViewModel {
5124418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik *     public final LiveData&lt;PagedList&lt;User>> usersList;
5224418e9aafa6ae3128ae47cf7087eda46dae4f5dChris Craik *     public MyViewModel(UserDao userDao) {
53895f27a719be103130ac2ba04ffedbeb600ef027Chris Craik *         usersList = new LivePagedListBuilder&lt;>(
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&lt;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&lt;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&lt;User> DIFF_CALLBACK =
864b1c5ed49b059d1494f8d0abec6d7b6e414f4057Chris Craik *             new DiffUtil.ItemCallback&lt;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 * @param  A 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