summaryrefslogtreecommitdiff
path: root/android/arch/paging/LivePagedListProvider.java
blob: 07dd84bf878735ddce7f0c2bae8b0f2fa016fc42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*
 * 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 android.arch.paging;

import android.arch.core.executor.ArchTaskExecutor;
import android.arch.lifecycle.ComputableLiveData;
import android.arch.lifecycle.LiveData;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;

/**
 * Provides a {@code LiveData<PagedList>}, given a means to construct a DataSource.
 * <p>
 * Return type for data-loading system of an application or library to produce a
 * {@code LiveData<PagedList>}, while leaving the details of the paging mechanism up to the
 * consumer.
 * <p>
 * If you're using Room, it can generate a LivePagedListProvider from a query:
 * <pre>
 * {@literal @}Dao
 * interface UserDao {
 *     {@literal @}Query("SELECT * FROM user ORDER BY lastName ASC")
 *     public abstract LivePagedListProvider&lt;Integer, User> usersByLastName();
 * }</pre>
 * In the above sample, {@code Integer} is used because it is the {@code Key} type of
 * {@link TiledDataSource}. Currently, Room can only generate a {@code LIMIT}/{@code OFFSET},
 * position based loader that uses TiledDataSource under the hood, and specifying {@code Integer}
 * here lets you pass an initial loading position as an integer.
 * <p>
 * In the future, Room plans to offer other key types to support paging content with a
 * {@link KeyedDataSource}.
 *
 * @param <Key> Type of input valued used to load data from the DataSource. Must be integer if
 *             you're using TiledDataSource.
 * @param <Value> Data type produced by the DataSource, and held by the PagedLists.
 *
 * @see PagedListAdapter
 * @see DataSource
 * @see PagedList
 */
public abstract class LivePagedListProvider<Key, Value> {

    /**
     * Construct a new data source to be wrapped in a new PagedList, which will be returned
     * through the LiveData.
     *
     * @return The data source.
     */
    @WorkerThread
    protected abstract DataSource<Key, Value> createDataSource();

    /**
     * Creates a LiveData of PagedLists, given the page size.
     * <p>
     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
     * {@link android.support.v7.widget.RecyclerView}.
     *
     * @param initialLoadKey Initial key used to load initial data from the data source.
     * @param pageSize Page size defining how many items are loaded from a data source at a time.
     *                 Recommended to be multiple times the size of item displayed at once.
     *
     * @return The LiveData of PagedLists.
     */
    @AnyThread
    @NonNull
    public LiveData<PagedList<Value>> create(@Nullable Key initialLoadKey, int pageSize) {
        return create(initialLoadKey,
                new PagedList.Config.Builder()
                        .setPageSize(pageSize)
                        .build());
    }

    /**
     * Creates a LiveData of PagedLists, given the PagedList.Config.
     * <p>
     * This LiveData can be passed to a {@link PagedListAdapter} to be displayed with a
     * {@link android.support.v7.widget.RecyclerView}.
     *
     * @param initialLoadKey Initial key to pass to the data source to initialize data with.
     * @param config PagedList.Config to use with created PagedLists. This specifies how the
     *               lists will load data.
     *
     * @return The LiveData of PagedLists.
     */
    @AnyThread
    @NonNull
    public LiveData<PagedList<Value>> create(@Nullable final Key initialLoadKey,
            final PagedList.Config config) {
        return new ComputableLiveData<PagedList<Value>>() {
            @Nullable
            private PagedList<Value> mList;
            @Nullable
            private DataSource<Key, Value> mDataSource;

            private final DataSource.InvalidatedCallback mCallback =
                    new DataSource.InvalidatedCallback() {
                @Override
                public void onInvalidated() {
                    invalidate();
                }
            };

            @Override
            protected PagedList<Value> compute() {
                @Nullable Key initializeKey = initialLoadKey;
                if (mList != null) {
                    //noinspection unchecked
                    initializeKey = (Key) mList.getLastKey();
                }

                do {
                    if (mDataSource != null) {
                        mDataSource.removeInvalidatedCallback(mCallback);
                    }

                    mDataSource = createDataSource();
                    mDataSource.addInvalidatedCallback(mCallback);

                    mList = new PagedList.Builder<Key, Value>()
                            .setDataSource(mDataSource)
                            .setMainThreadExecutor(ArchTaskExecutor.getMainThreadExecutor())
                            .setBackgroundThreadExecutor(
                                    ArchTaskExecutor.getIOThreadExecutor())
                            .setConfig(config)
                            .setInitialKey(initializeKey)
                            .build();
                } while (mList.isDetached());
                return mList;
            }
        }.getLiveData();
    }
}