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&lt;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&lt;Integer, User> usersByLastName();
48 * }
49 *
50 * class MyViewModel extends ViewModel {
51 *     public final LiveData&lt;PagedList&lt;User>> usersList;
52 *     public MyViewModel(UserDao userDao) {
53 *         usersList = new LivePagedListBuilder&lt;>(
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&lt;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&lt;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&lt;User> DIFF_CALLBACK =
86 *             new DiffUtil.ItemCallback&lt;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 * @param  A 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