Before reading this article, you may want to read the following articles in the same series on Android Jetpack components:
- Android Jetpack Component: Lifecycle
- Android Jetpack Component: LiveData
- Android Jetpack Component: ViewModel
- Android Jetpack Component: DataBinding
- Android Jetpack Component: BindingAdapter
- Android Jetpack Component: Using Observable Data Objects
This article will introduce the use of the Paging Library, with source code analysis to be covered in the next article. The Paging Library component is part of Android Jetpack and is Google's official pagination component. If your project uses Google's newly released official architecture components, such as LiveData, Lifecycle, ViewModel, etc., you can try to integrate this pagination component into your project. Its advantage is seamless loading of more data, which improves user experience to some extent.
Here’s a brief overview of the process of using the paging component to load data in pages: DataSource is responsible for loading data from the network or database and storing it in PagedList. The data is submitted to PagedListAdapter using submitList. When the data changes, the differences are calculated in the background thread, and finally, PagedListAdapter notifies RecyclerView to update the data.
- Prepare data
- Introduce Paging Library component
- Customize DataSource
- Configure pagination parameters
- Load and display data
- Test the effect
- Source code analysis of Paging Library
Prepare Data#
Here, we will use the open-source API from Gank.io for testing, as follows:
public interface CommonAPI {
// Using Gank.io open-source API: http://gank.io/api/search/query/listview/category/Android/count/10/page/1
@GET("api/search/query/listview/category/Android/count/8/page/{page}")
Call<List<DataBean.ResultsBean>> getArticleList1(@Path("page") int page);
}
Introduce Paging Library Component#
Introduce the Paging Library as follows:
def paging_version = "2.1.0"
// androidx
implementation "androidx.paging:paging-runtime:$paging_version"
// Old version (page library 2.0.0-rc01)
implementation "android.arch.paging:runtime:$paging_version"
Here, we are using the latest version of androidx.
Customize DataSource#
Customize DataSource to load data. Using PageKeyedDataSource is more appropriate for loading network data. Inherit PageKeyedDataSource to customize DataSource as follows:
// Custom DataSource
public class MDataSource extends PageKeyedDataSource<String, DataBean.ResultsBean> {
private static final String TAG = MDataSource.class.getSimpleName();
private int mPage = 1;
public MDataSource() {
}
// Initialize
@Override
public void loadInitial(@NonNull LoadInitialParams<String> params,
@NonNull final LoadInitialCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadInitial-->");
CommonAPI api = RetrofitApi.getInstance().mRetrofit.create(CommonAPI.class);
Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
@Override
public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
Log.i(TAG, "--onResponse-->" + response.toString());
if (response.isSuccessful() && response.code() == 200) {
List<DataBean.ResultsBean> data = response.body();
callback.onResult(data, "before", "after");
}
}
@Override
public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
Log.i(TAG, "--onFailure-->" + t.getMessage());
}
});
}
// Load previous page
@Override
public void loadBefore(@NonNull LoadParams<String> params,
@NonNull LoadCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadBefore-->" + params.key);
}
// Load next page
@Override
public void loadAfter(@NonNull final LoadParams<String> params,
@NonNull final LoadCallback<String, DataBean.ResultsBean> callback) {
Log.i(TAG, "--loadAfter-->" + params.key);
mPage++;
CommonAPI api = RetrofitApi.getInstance().mRetrofit.create(CommonAPI.class);
Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
@Override
public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
Log.i(TAG, "--onResponse-->" + response.toString());
if (response.isSuccessful() && response.code() == 200) {
List<DataBean.ResultsBean> data = response.body();
callback.onResult(data, params.key);
}
}
@Override
public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
Log.i(TAG, "--onFailure-->" + t.getMessage());
}
});
}
}
It's simple with no extra elements. For details, refer to the source code analysis later in the article. Create a factory to facilitate the creation of a new DataSource when data changes as follows:
// MDataSource creation factory
public class MDataSourceFactory extends DataSource.Factory<String, DataBean.ResultsBean> {
public MDataSourceFactory() {
}
@NonNull
@Override
public DataSource<String, DataBean.ResultsBean> create() {
MDataSource mDataSource = new MDataSource();
return mDataSource;
}
}
Configure Pagination Parameters#
In ViewModel, create PagedList.Config and configure pagination parameters. Create a DataSource factory object, and finally generate LiveData data that supports pagination, as follows:
// ViewModel
public class MViewModel extends ViewModel {
private int pageSize = 20;
// PagedList configuration
private PagedList.Config config = new PagedList.Config.Builder()
.setInitialLoadSizeHint(pageSize) // Set the number of items to load initially
.setPageSize(pageSize) // Set the number of items to load per page
.setPrefetchDistance(2) // Set the distance from the last item to prefetch the next page
.setEnablePlaceholders(false) // Set whether to enable UI placeholders
.build();
// DataSource.Factory
private DataSource.Factory<String, DataBean.ResultsBean> factory = new MDataSourceFactory();
// LiveData
private LiveData<PagedList<DataBean.ResultsBean>> mPagedList = new LivePagedListBuilder<>(factory, config)
.build();
public LiveData<PagedList<DataBean.ResultsBean>> getPagedList() {
return mPagedList;
}
}
Load and Display Data#
Here, we use LiveData to observe the loaded data, and then use submitList to submit the data to PagedListAdapter. It will compare the differences between the new and old data in the background thread and finally update the RecyclerView, as follows:
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private RecyclerView mRecyclerView;
private ArticleAdapter mAdapter;
private MViewModel mViewModel;
private static DiffUtil.ItemCallback<DataBean.ResultsBean> itemCallback = new DiffUtil.ItemCallback<DataBean.ResultsBean>() {
@Override
public boolean areItemsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
return oldItem.getGanhuo_id() == newItem.getGanhuo_id();
}
@Override
public boolean areContentsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
return oldItem.equals(newItem);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.rvData);
mAdapter = new ArticleAdapter(itemCallback);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mAdapter);
ViewModelProvider mViewModelProvider = new ViewModelProvider(this,
new ViewModelProvider.AndroidViewModelFactory(getApplication()));
mViewModel = mViewModelProvider.get(MViewModel.class);
}
public void getData(View view) {
mViewModel.getPagedList().observe(this, new Observer<PagedList<DataBean.ResultsBean>>() {
@Override
public void onChanged(PagedList<DataBean.ResultsBean> dataBeans) {
Log.i(TAG, "--onChanged-->");
mAdapter.submitList(dataBeans);
}
});
}
}
Test the Effect#
This is the usage of the Paging Library.
Source Code Analysis of Paging Library#
Next, we will start with the observer method of LiveData. The source code is as follows:
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread("observe");
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
// Remember this LifecycleBoundObserver for later use
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
// Add Observer, specifically LifecycleBoundObserver
owner.getLifecycle().addObserver(wrapper);
}
Continue to look at the addObserver method of LifecycleRegistry, the source code is as follows:
@Override
public void addObserver(@NonNull LifecycleObserver observer) {
// ...
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
// The dispatchEvent method of ObserverWithState
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / sibling may have been changed recalculate
targetState = calculateTargetState(observer);
}
// ...
}
static class ObserverWithState {
State mState;
LifecycleEventObserver mLifecycleObserver;
ObserverWithState(LifecycleObserver observer, State initialState) {
mLifecycleObserver = Lifecycling.lifecycleEventObserver(observer);
mState = initialState;
}
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = getStateAfter(event);
mState = min(mState, newState);
// The actual call is to the onStateChanged method of LifecycleBoundObserver
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
}
Next, look at the onStateChanged method of the internal class LifecycleBoundObserver in LiveData, the source code is as follows:
// Method
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
// Lifecycle component listening to lifecycle method callback
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
// According to the source code shouldBeActive method, as long as the Activity's state is ON_START or later, it returns true
activeStateChanged(shouldBeActive());
}
// activeStateChanged method
void activeStateChanged(boolean newActive) {
// mActive defaults to false, so it does not hold
// If executed a second time with mActive as false, it will not continue processing, resulting in not returning to the onChanged method
if (newActive == mActive) {
return;
}
// After the first execution, mActive will be set to true
mActive = newActive;
// As long as there are active observers, meaning the component's state is START or RESUME, mActiveCount will not equal 0, wasInactive will be true
boolean wasInactive = LiveData.this.mActiveCount == 0;
// mActiveCount is greater than 0
LiveData.this.mActiveCount += mActive ? 1 : -1;
// wasInactive and mActive are both true, execute
if (wasInactive && mActive) {
onActive();
}
// mActiveCount is greater than 0, false && false, do not execute
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
// mActive is true
if (mActive) {
// Dispatch and process ObserverWrapper, which is the added LifecycleBoundObserver
dispatchingValue(this);
}
}
At this point, we can at least explain why the onChanged method is called only once during the initial data loading of PagedList, while it does not call onChanged when loading the next page of data.
Next, look at the dispatchingValue method of LiveData, the source code is as follows:
// dispatchingValue method
void dispatchingValue(@Nullable ObserverWrapper initiator) {
// ...
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
// Iterate to get Observer processing
considerNotify(iterator.next().getValue());
//...
}
// ...
}
// considerNotify method
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
// If the observer is not in an active state, do not notify the observer
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
// Notify the observer
observer.mObserver.onChanged((T) mData);
}
At this point, the result is returned to the specific observer.
Starting from the creation of mPagedList, the code for creating mPagedList is as follows:
// LiveData
private LiveData<PagedList<DataBean.ResultsBean>> mPagedList = new LivePagedListBuilder<>(factory,config)
.build();
Continue to look at the key method build, the source code is as follows:
// build method
public LiveData<PagedList<Value>> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
// create method
private static <Key, Value> LiveData<PagedList<Value>> create(
return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
// DataSource invalidation callback
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
// This callback usually indicates that a new data source is needed
invalidate();
}
};
@SuppressWarnings("unchecked") // for casting getLastKey to Key
@Override
protected PagedList<Value> compute() {
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
initializeKey = (Key) mList.getLastKey();
}
do {
if (mDataSource != null) {
mDataSource.removeInvalidatedCallback(mCallback);
}
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
// Create PagedList, how PagedList is created and stored will be discussed later
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached()); // When DataSource is invalid, use the previous data; when DataSource is valid, execute once and return PagedList
return mList;
}
}.getLiveData();
}
At this point, LiveData<PagedList> has been analyzed from creation to update.
Next, continue to look at how PagedList is generated. The key source code for creating PagedList is as follows:
// Creation of PagedList
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
// Continue to look at the build method, which calls the create method of PagedList
public PagedList<Value> build() {
// ...
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
}
// Truly creating PagedList
static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
@NonNull Executor notifyExecutor,
@NonNull Executor fetchExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
@Nullable K key) {
// PageKeyedDataSource inherits ContiguousDataSource, dataSource.isContiguous() is true
if (dataSource.isContiguous() || !config.enablePlaceholders) {
int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
// Using PageKeyedDataSource will not execute
if (!dataSource.isContiguous()) {
//noinspection unchecked
dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
.wrapAsContiguousWithoutPlaceholders();
if (key != null) {
lastLoad = (Integer) key;
}
}
// Create and return ContiguousPagedList, which is PagedList
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
return new ContiguousPagedList<>(contigDataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
key,
lastLoad);
} else {
return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
(key != null) ? (Integer) key : 0);
}
}
Continue to look at the creation of ContiguousPagedList, the key source code is as follows:
ContiguousPagedList(
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key,
int lastLoad) {
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
// ...
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
// ...
}
Here, the dispatchLoadInitial method of the abstract class ContiguousDataSource is called. Look at the specific implementation of this method in PageKeyedDataSource, the source code is as follows:
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
// Create LoadInitialCallback, which is the callback used in the custom DataSource when loading data from the network
LoadInitialCallbackImpl<Key, Value> callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
// The custom DataSource needs to implement the loadInitial method, which completes the callback here
loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
// Set the callback to execute on the main thread
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
At this point, we still do not know how the data returned is saved, so continue to look at the specific implementation and callback of the callback, the source code is as follows:
static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {
final LoadCallbackHelper<Value> mCallbackHelper;
private final PageKeyedDataSource<Key, Value> mDataSource;
private final boolean mCountingEnabled;
// LoadInitialCallbackImpl constructor
LoadInitialCallbackImpl(@NonNull PageKeyedDataSource<Key, Value> dataSource,
boolean countingEnabled, @NonNull PageResult.Receiver<Value> receiver) {
// Here, it is necessary to remember that ResultType is PageResult.INIT, which is the data type when loading data initially, encountered PageResult.INIT later
mCallbackHelper = new LoadCallbackHelper<>(
dataSource, PageResult.INIT, null, receiver);
mDataSource = dataSource;
mCountingEnabled = countingEnabled;
}
// This onResult callback is obviously not used when setting data in PageKeyedDataSource, it should be used by PositionalDataSource, which will be skipped
@Override
public void onResult(@NonNull List<Value> data, int position, int totalCount,
@Nullable Key previousPageKey, @Nullable Key nextPageKey) {
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
// Setup keys before dispatching data, so guaranteed to be ready
mDataSource.initKeys(previousPageKey, nextPageKey);
int trailingUnloadedCount = totalCount - position - data.size();
if (mCountingEnabled) {
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(
data, position, trailingUnloadedCount, 0));
} else {
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
}
}
}
// This onResult callback is used when setting data in PageKeyedDataSource
@Override
public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
@Nullable Key nextPageKey) {
// When DataSource is valid, dispatchInvalidResultIfInvalid returns false
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
// Initialize previousPageKey and nextPageKey set in loadInitial, which should be used to distinguish loading the previous page or the next page
mDataSource.initKeys(previousPageKey, nextPageKey);
// Dispatch the returned data
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
}
}
}
Here we have obtained the data loaded from the network. Continue to look at the dispatchResultToReceiver method to see how the data is processed, the source code is as follows:
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
Executor executor;
synchronized (mSignalLock) {
if (mHasSignalled) {
throw new IllegalStateException(
"callback.onResult already called, cannot call again.");
}
mHasSignalled = true;
executor = mPostExecutor;
}
// If executor is null, because in the above analysis, the callback executor is assigned and run on the main thread
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
// Continue to callback the data
mReceiver.onPageResult(mResultType, result);
}
});
} else { // Non-main thread direct callback
mReceiver.onPageResult(mResultType, result);
}
}
Here, continue to look at the specific implementation of onPageResult. From the source code, we know that this process is implemented in ContiguousPagedList, the specific source code is as follows:
PageResult.Receiver<V> mReceiver = new PageResult.Receiver<V>() {
@AnyThread
@Override
public void onPageResult(@PageResult.ResultType int resultType,
@NonNull PageResult<V> pageResult) {
// ...
List<V> page = pageResult.page;
// Previously, during the initial loading of data, the data state was PageResult.INIT
// That is, when calling loadInitial
if (resultType == PageResult.INIT) {
// 1. Save data to PagedStorage
// 2. Finally call RecyclerView.Adapter's notifyItemRangeInserted(position, count) method, position is 0
mStorage.init(pageResult.leadingNulls, page, pageResult.trailingNulls,
pageResult.positionOffset, ContiguousPagedList.this);
// ...
} else {
// ...
// When creating LoadCallbackHelper, the data state is PageResult.APPEND
// That is, when calling loadAfter
if (resultType == PageResult.APPEND) {
if (skipNewPage && !trimFromFront) {
// don't append this data, drop it
mAppendItemsRequested = 0;
mAppendWorkerState = READY_TO_FETCH;
} else {
// 1. Save data to PagedStorage
// 2. Finally callback loadAfter
mStorage.appendPage(page, ContiguousPagedList.this);
}
// When creating LoadCallbackHelper, the data state is PageResult.PREPEND
// That is, when calling loadBefore
} else if (resultType == PageResult.PREPEND) {
if (skipNewPage && trimFromFront) {
// don't append this data, drop it
mPrependItemsRequested = 0;
mPrependWorkerState = READY_TO_FETCH;
} else {
// 1. Save data to PagedStorage
// 2. Finally callback loadBefore
mStorage.prependPage(page, ContiguousPagedList.this);
}
} else {
throw new IllegalArgumentException("unexpected resultType " + resultType);
}
// ...
}
};
At this point, the creation and saving process of PagedList has concluded. Previously, we analyzed that the data callback from the observer would call the onChanged method, in which the data to be displayed is set for the RecyclerView's Adapter, as follows:
mViewModel.getPagedList().observe(this, new Observer<PagedList<DataBean.ResultsBean>>() {
@Override
public void onChanged(PagedList<DataBean.ResultsBean> dataBeans) {
// Set the data to be displayed, will only be called once during initialization
// If there is already a displayed list, it will calculate the differences in data in the background thread and notify data updates
mAdapter.submitList(dataBeans);
}
});
As mentioned above, if there is already a displayed list, it will calculate the differences in data in the background thread and notify data updates. So where is this background thread? The submitList method of mAdapter ultimately calls the following submitList method, which uses AsyncDifferConfig to obtain the background thread for executing data difference calculations. If not specified, this background thread pool is a fixed size of 2, the source code is as follows:
@SuppressWarnings("ReferenceEquality")
public void submitList(@Nullable final PagedList<T> pagedList,
@Nullable final Runnable commitCallback) {
// ...
final PagedList<T> oldSnapshot = mSnapshot;
final PagedList<T> newSnapshot = (PagedList<T>) pagedList.snapshot();
// Get the background thread pool for executing data difference calculations and callback the results to the main thread
mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult result;
result = PagedStorageDiffHelper.computeDiff(
oldSnapshot.mStorage,
newSnapshot.mStorage,
mConfig.getDiffCallback());
// Callback the calculation results to the main thread, then notify the dataset updates, thus notifying RecyclerView to update data
mMainThreadExecutor.execute(new Runnable() {
@Override
public void run() {
if (mMaxScheduledGeneration == runGeneration) {
latchPagedList(pagedList, newSnapshot, result,
oldSnapshot.mLastLoad, commitCallback);
}
}
});
}
});
}
Continue to look at the latchPagedList source code, here will perform data update operations and read data, the source code is as follows:
void latchPagedList(
@NonNull PagedList<T> newList,
@NonNull PagedList<T> diffSnapshot,
@NonNull DiffUtil.DiffResult diffResult,
int lastAccessIndex,
@Nullable Runnable commitCallback) {
// ...
// Update mPageList and notify RecyclerView to update data
PagedStorageDiffHelper.dispatchDiff(mUpdateCallback,
previousSnapshot.mStorage, newList.mStorage, diffResult);
newList.addWeakCallback(diffSnapshot, mPagedListCallback);
if (!mPagedList.isEmpty()) {
// Read data
mPagedList.loadAround(Math.max(0, Math.min(mPagedList.size() - 1, newPosition)));
}
onCurrentListChanged(previousSnapshot, mPagedList, commitCallback);
}
The dispatchDiff method will ultimately call the dispatchLastEvent method, the source code is as follows:
public void dispatchLastEvent() {
if (mLastEventType == TYPE_NONE) {
return;
}
switch (mLastEventType) {
case TYPE_ADD:
mWrapped.onInserted(mLastEventPosition, mLastEventCount);
break;
case TYPE_REMOVE:
mWrapped.onRemoved(mLastEventPosition, mLastEventCount);
break;
case TYPE_CHANGE:
mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload);
break;
}
mLastEventPayload = null;
mLastEventType = TYPE_NONE;
}
mWrapped is a ListUpdateCallback, and AdapterListUpdateCallback implements this interface and notifies the RecyclerView's Adapter to perform data updates.
In the latchPagedList method, the loadAround method of PagedList will be called to obtain data, which is implemented using the loadBefore and loadAfter methods in PageKeyedDataSource. The source code will not be pasted here.
The pagination loading process of the Page Library has been analyzed. From the source code, we can see that the onChanged method is only called once during initialization, but it can continuously load the next page of data.