Author: Angelicas

Address: https://www.jianshu.com/p/2e1dc6e7278b

An overview,

Jetpack was unveiled at Google I/O 2018 on May 9, building on the popular architectural components released last year. Jetpack helps us focus on improving the app experience, speeding up app development, handling things like background tasks, UI navigation, and lifecycle management. The new Android Jetpack component has been released with four updates:

WorkManager, Paging, Navigation, and Slices.

Today we are going to talk about Paging. When conducting big data query, Paging component enables us to request data loading from local or network in a progressive manner and make applications capable of processing large data without increasing device burden or waiting time much. That includes support for RecycleView. As usual, I would like to summarize the learning process of Android official Paging Library and some points needing attention.

Check out Google’s official documentation for details:

https://developer.android.com/topic/libraries/architecture/paging/

Introduction to Paging Libray

The relationship between DataSource, PagedList, and PagedAdapter in Paging Library and the process of loading data to data display:

As shown in the figure below, the data flow of Paging is produced in the background thread, where most of the work is done and displayed in the UI thread.

For example, when a new item is inserted into the database, the DataSource is initialized, and the LiveData<PagedList> background thread will create a new PagedList. This new PagedList is sent to the PagedListAdapter of the UI thread. The PagedListAdapter uses DiffUtil to compare the difference between the current Item and the new Item. When the end of contrast, PagedListAdapter by calling RecycleView. Adapter. NotifyItemInserted () insert a new item to the appropriate location. The RecycleView will know that it needs to bind a new item and display it.

From the code level, we need to set the PagedListAdater to Recyclerview, PagedListAdapter set the corresponding PagedList. Each time the Adapter getItem lets the PagedList know how many items we have slipped to. The PagedList calculates these numbers and the configured parameters. When the condition is met, the PagedList informs the DataSource to return the data. When the data is returned successfully, the PagedListAdapter is notified to refresh.

Datasource

A Datasource is a class related to a Datasource. For different scenarios, Paging provides three Datasource types:

  1. PageKeyedDataSource: If a page inserts a/next key at load time, for example, to get social media posts from the network, nextPage may need to be loaded into a subsequent load;

  2. ItemKeyedDataSource: used when the number of data items used increases from N to N+1.

  3. PositionalDataSource: If you need to fetch the data page from any location in the data store. This class allows you to request a data set of items from any location. For example, the request might return 20 data items starting at position 1200;

Choose different abstract classes to implement DataSource according to the usage scenario. All three Datasource types need to implement the loadInitial() method, each encapsulating the parameter type LoadInitialParams that requested the initialization data. PageKeyedDataSource and ItemKeyedDataSource are similar. LoadBefore () and loadAfter() methods are implemented. Namely LoadParams < Key >. PositionalDataSource needs to implement loadRange().

PagedList

The PagedList uses Datasource to load data. You can configure Config to load data ata time, preload data, etc. In addition, PagedList can also send a signal to recyclerView. Adapter to update the UI. Config: configures the PagedList to load data from the Datasource, including the following properties:

  1. SetPageSize: Sets the number of pages to load;

  2. SetInitialLoadSizeHint: Sets the number of loads for the first time;

  3. SetPrefetchDistance: Sets the number of items left at the end of the distance, that is, the pager library starts to load the data of the next page;

  4. Setenableplaceholder: Indicates whether a NULL placeholder is set;

PagedListAdapter

PagedListAdapter is an implementation of RecyclerView.Adapter, used to display PagedList data. Each getItem in the PagedListAdapter lets the PagedList know how many items we have slipped to. When the DataSource provides a new PagedList, the PagedList calculates these numbers and the configured parameters. When the condition is met, the PagedList informs the DataSource. Let it return data. When the data is returned successfully, the PagedListAdapter is notified. The PagedListAdapter uses DiffUtil to compare the difference between the current Item and the new Item and refresh it.

Type of data source loaded

There are two main ways to load data:

Single data source: local data or network data

First we can use LivePagedListBuilder to create LiveData to provide data for the UI layer. If the data source is DB, DB pushes a new PagedList (a LiveData dependent mechanism) when the data changes. In the case of network data, the invalidate() method of the Datasource is called with a swipe refresh to load the new data, since there is no way to know how the Datasource has changed. The process is as follows:

Multiple data sources: local data + network data

Generally, load local data first, and then load network data. For example, when you open the IM chat interface, load the chat messages in the local database first, and then load the offline messages on the network. At this point, we need to set BoundaryCallback for PagedList to monitor whether the local data is loaded. When the local data is loaded, network data will be loaded and then stored in the library. At this point, LiveData will push a new PagedList and trigger interface refresh.

Four, the implementation of the code

As usual, let’s take a look at the renderings

You can see from the renderings, when RecyclerView sliding, trigger page loading, the RecyclerView follow-up to use the data page loading display. LoadAfter = loadAfter = loadAfter = loadAfter = loadAfter = loadAfter = loadAfter = loadAfter = loadAfter = loadAfter When we swipe the second page, it will request data from the third page before we swipe to the bottom, and so on. Ok, let’s take a look at the code:

Add the dependent

To use the Android Paging Library first, we need to add a reference to the Paging support Library and other libraries I used in Dome to the build.gradle of our application (note: this article is based on the 1.0.0 version of Paging Library). :

/ / pagingimplementation 'android. The arch. The paging: runtime: 1.0.0' / / RxKotlinimplementation 'the IO. Reactivex. Rxjava2: rxkotlin: 2.2.0' / / RxAndroidimplementation 'IO. Reactivex. Rxjava2: rxandroid: 2.0.2' / / Lifecycleimplementation 'android. Arch. Lifecycle: extensions: 1.1.1'Copy the code

StudentDataSourceFactory:

To create these observable PagedList objects, we need to give our DataSource.Factory to a LivePagedListBuilder object. A DataSource object loads the PagedList data for a single page. The factory class creates a new instance of PagedList in response to content updates, such as database table failures and network refreshes.

/** * A simple data source factory, which provides a way to observe the data source created last time, */ Class StudentDataSourceFactory(private Val retryExecutor: Executor): DataSource.Factory<String, StudentBean>() { val sourceLiveData=MutableLiveData<StudentDataSource>() override fun create(): DataSource<String, StudentBean> { val source= StudentDataSource(retryExecutor) sourceLiveData.postValue(source) return source }}Copy the code

StudentDataRepository:

/** 1. The Repository implementation returns a Listing that loads data directly from the network. */class StudentDataRepository(private val executor: executor): class StudentDataRepository(private val executor: executor); StudentRepository { override fun getStudentList(pageSize: Int): Listing<StudentBean? > { val sourceFactory= StudentDataSourceFactory(executor) val pagedListConfig = PagedList.Config.Builder() Setenableplaceholder (false).setInitiAlloadSizeHint (pageSize*2)// Define how many items will be loaded on the first page .setPagesize (pageSize)// Define the number of items loaded from the DataSource ata time. Build () val pagedList = LivePagedListBuilder(sourceFactory, PagedListConfig).setFetchExecutor(executor)// Sets the Executor executor to be used to fetch PagedLists data from the DataSources. If not set, default to the Arch component I/O thread. .build() val refreshState = switchMap(sourceFactory.sourceLiveData) { it.initialLoad } return Listing<StudentBean? >( pagedList =pagedList, networkState = switchMap(sourceFactory.sourceLiveData, { it.networkState }), retry = { sourceFactory.sourceLiveData.value?.retryAllFailed() }, refresh = { sourceFactory.sourceLiveData.value?.invalidate() }, refreshState = refreshState) }}Copy the code

The Shared interface

Define a common interface to be shared between different Repository implementations:

/** 3. Repository implements shared interfaces for different repositories 4. Created by Juan on 2018/05/23 getStudentList(pageSize: Int): Listing<StudentBean? >}Copy the code

Select the correct data source type:

We use DataSource to create our own paged DataSource, including the definition of loading the first page and each subsequent page of data:

/** * DataSource(private val); /** * DataSource(private val); /** * DataSource(private val); /** * DataSource(private val) retryExecutor: Executor) : ItemKeyedDataSource<String, StudentBean>(){ private var TAG: String="paging" private var retry:(()->Any)? =null private var startPosition:Int = 0 fun retryAllFailed(){ val prevRetry=retry retry=null prevRetry? .let { retryExecutor.execute { it.invoke() } } } val networkState by lazy{ MutableLiveData<Resource<String>>() } val InitialLoad by lazy{MutableLiveData<Resource<String>>()} /** * Receive the data initially loaded, Here we need to call back the data to onResult of LoadInitialCallback to initialize the PagedList and count the loaded items */ Override fun loadInitial(params: LoadInitialParams<String>, callback: LoadInitialCallback<StudentBean>) { Log.d(TAG,"loadInitial->mSkip:"+startPosition+",count:"+params.requestedLoadSize) Networkstate.postvalue (resource-.loading (null)) InitialLoad.postValue (resource-.loading (null)) // Simulate time-consuming operation val list=loadData(startPosition,params.requestedLoadSize) retry=null networkState.postValue(Resource.success(null)) Initialload.postvalue (resource-.success (null)) callback.onResult(list) startPosition+=list.size} /** * Receive loaded data */ override fun loadAfter(params: LoadParams<String>, callback: LoadCallback<StudentBean>) { Log.d(TAG,"loadAfter->mSkip:"+startPosition+",count:"+params.requestedLoadSize) NetworkState. PostValue (Resource loading (null)) / / simulation time-consuming operation val list = loadData (startPosition, params. RequestedLoadSize) retry=null networkState.postValue(Resource.success(null)) callback.onResult(list) startPosition+=list.size } override fun loadBefore(params: LoadParams<String>, callback: LoadCallback<StudentBean>) { Log.d(TAG,"loadBefore") } override fun getKey(item: StudentBean): String = item.id!! /** * simulates time-consuming operations, assuming that some background thread data loading is required. */ private fun loadData(startPosition: Int, limit: Int): List<StudentBean> { val list = ArrayList<StudentBean>() for (i in 0 until limit) { var position=startPosition + i val Data = StudentBean(position.toString(), "student @$position") list.add(data)} return list}}Copy the code

ItemKeyedDataSource implements the following methods:

  1. LoadInitial (@nonNULL LoadInitialParams<Key> params, @nonNULL LoadInitialCallback<Value> callback) -> Here you need to call back the retrieved data through onResult of LoadInitialCallback to initiate the PagedList and count the loaded items

  2. LoadAfter (@nonnull LoadParams<Key> params, @nonNULL LoadCallback<Value> callback) -> is used to receive data for each subsequent page, using the same method as loadInitial

  3. LoadBefore (@nonNULL LoadParams<Key> params, @nonNULL LoadCallback<Value> callback) -> Load list data before the specified Key

  4. GetKey (@nonNULL Value item) -> Returns the key associated with the given item

Listing

Data classes necessary for UI display lists to interact with the rest of the system:

/** Created by Juan on 2018/05/23. */data class Listing<T> (val pagedList: LiveData<PagedList<T>>, val networkState: LiveData<Resource<String>>, val refreshState: LiveData<Resource<String>>, val refresh: () -> Unit, val retry: () -> Unit)Copy the code

Resource

A generic class for holding values that have their loaded state:

Created by Juan on 2018/05/23. */ Data class Resource<out T>(val status: status, val data: T? , val message: String?) { companion object { fun <T> loading(msg: String? = null, data: T? = null): Resource<T> { return Resource(Status.LOADING, data, msg) } fun <T> success(data: T? = null): Resource<T> { return Resource(Status.SUCCESS, data, null) } fun <T> error(msg: String? = null, data: T? = null): Resource<T> { return Resource(Status.ERROR, data, msg) } }}enum class Status { SUCCESS, ERROR, LOADING}Copy the code

StudentViewModel

Dome is an application framework that references MVVM. StudentViewModel only does business logic and business data. It doesn’t do anything uI-related. The ViewModel layer does not hold references to any controls, and does not update the UI through references to UI controls in the ViewModel. To put it simply, it focuses on the logical processing of business and only operates on data. I’ll probably write an article on MVVM later

/** * Created by juan on 2018/05/23. */class StudentViewModel : AndroidViewModel { private lateinit var mPostRepository: StudentRepository constructor(application: Application, postRepository: StudentRepository):super(application){this.mpostrepository =postRepository} // region Based on the Android official Paging Library Paging load framework private val data = MutableLiveData<String>() private val repoResult = Transformations.map(data, { mPostRepository.getStudentList(10) }) val posts = Transformations.switchMap(repoResult, { it.pagedList })!! val networkState = Transformations.switchMap(repoResult, { it.networkState })!! val refreshState = Transformations.switchMap(repoResult, { it.refreshState })!! fun refresh() { repoResult.value? .refresh? .invoke() } fun showDatas(subreddit: String): Boolean { if (data.value == subreddit) { return false } data.value = subreddit return true } fun retry() { val listing =  repoResult? .value listing? .retry? .invoke() } fun currentData(): String? = data.value // endregion}Copy the code

MainActivity

The Activity here is to initialize some controls (such as control color, add RecyclerView separations, etc.), subscribe to the loading Status of Resource.Status, so as to update the UI. Simply put: The View layer does not do any business logic, does not involve manipulation or processing of data.

class MainActivity : AppCompatActivity() { private lateinit var studentViewModel: StudentViewModel private lateinit var mAdapter: StudentAdapter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) studentViewModel=getViewModel() initAdapter() initSwipeToRefresh() studentViewModel.showDatas("") } private fun getViewModel(): StudentViewModel { return ViewModelProviders.of(this, object : ViewModelProvider.Factory { override fun <T : ViewModel? > create(modelClass: Class<T>): T { val repo = ServiceLocator.instance() .getRepository() return StudentViewModel(application, repo) as T } })[StudentViewModel::class.java] } private fun initSwipeToRefresh() { swipeRefreshLayout.setColorSchemeColors(Color.BLUE, Color.GREEN, Color.RED, Color.YELLOW) swipeRefreshLayout.setOnRefreshListener { studentViewModel.refresh() } studentViewModel.refreshState.observe(this, Observer { resource-> if (resource==null){ return@Observer } when(resource.status){ Status.LOADING->{ swipeRefreshLayout.isRefreshing=true } Status.SUCCESS->{ swipeRefreshLayout.isRefreshing=false } Status.ERROR->{ Toast.makeText(this,resource.message,Toast.LENGTH_LONG).show() swipeRefreshLayout.isRefreshing=false } } }) } private fun initAdapter() { val mLayoutManager = LinearLayoutManager(this) mLayoutManager.orientation = LinearLayout.VERTICAL rv!! .layoutManager = mLayoutManager rv.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration. VERTICAL)) / / add divider mAdapter = StudentAdapter (this) the rv. Adapter = mAdapter studentViewModel.posts.observe(this, Observer((mAdapter::submitList))) }}Copy the code

The studentViewModel. Posts. Observe (this, the Observer ((mAdapter: : submitList))) is the LiveData < PagedList > associated with PagedListAdapter connection.

StudentAdapter

/** * Created by juan on 2018/05/23. */class StudentAdapter(private val mContext: Context):PagedListAdapter<StudentBean, StudentViewHolder>(diffCallback){    override fun onBindViewHolder(holder: StudentViewHolder, position: Int) {        holder.bindTo(getItem(position),mContext)    }    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StudentViewHolder = StudentViewHolder(parent)    companion object {        private val diffCallback = object : DiffUtil.ItemCallback<StudentBean>() {            override fun areItemsTheSame(oldItem: StudentBean, newItem: StudentBean): Boolean =                    oldItem.id == newItem.id            override fun areContentsTheSame(oldItem: StudentBean, newItem: StudentBean): Boolean =                    oldItem == newItem        }    }}Copy the code

I will record the learning process of Android Paging Library at this stage as a phased memo of my learning of Android Paging Library technology. This code still needs to be further improved and followed up.

Have what question, please leave a message below, have inadequacy place still hope to guide, thank everybody.

Download the source code

Appendix: 1, Google Android’s official Android the Paging Library technical documentation homepage: https://developer.android.com/topic/libraries/architecture/paging/

2, Google samples: https://github.com/googlesamples/android-architecture-components

Recent Articles:

  • Workplace 90 post: the highest level of stability, is to have the ability to review

  • Routine jokes from a programming ape

  • Welfare! Code an egg to give you a wave of advanced experience

Question of the day:

Android developer, did you get in the Jetpack car?

Message format:

Punch x days, answer: XXX.

Participate in clocking activities:

  Poke me for details