[Học Android] Tìm hiểu Paging Library

[Học Android] Tìm hiểu Paging Library

Tìm hiểu thư viện phân trang Paging Library trong bộ Android JetPack của Android

Giới thiệu

Paging được Google mới cho ra mắt trong bộ Android JetPack với thành phần chính gồm: DataSource, PagedList PagedListAdapter. Trong bài viết này chủ yếu mình muốn hướng dẫn bạn cách sử dụng Paging thông qua một ví dụ đơn giản.

Sử dụng

1. Thêm Paging vào dependencies build.gradle trong module:

dependencies {
  ...
  implementation "android.arch.paging:runtime:1.0.1"
  implementation "android.arch.paging:rxjava2:1.0.1"
}

2. Tạo file Service để thực hiện request

interface GithubService {

    @GET("/users")
    fun getUsers(@Query("since") userId: Long, @Query("per_page") perPage: Int): Single>

    companion object {
        fun getService(): GithubService {
            val retrofit = Retrofit.Builder()
                    .baseUrl("https://api.github.com/")
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build()
            return retrofit.create(GithubService::class.java)
        }
    }
}

3. Tạo file UsersDataSource

File này implement ItemKeyedDataSource để override lại 3 phương thức chính để tạo nên Paging đó là :

  • loadInitial: Như tên của nó thì nó sẽ gọi khi DataSource được khởi tạo
  • loadBefore: Sẽ được gọi sau loadInitial
  • loadAfter: Sẽ được gọi khi list của bạn đạt tới limit được khai báo

Code của mình thì sẽ như này:

class UsersDataSource(private val githubService: GithubService,
    private val compositeDisposable: CompositeDisposable) : ItemKeyedDataSource() {

    val isLoading = MutableLiveData()
    val isRefresh = MutableLiveData()

    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback) {
        isLoading.postValue(true)
        isRefresh.postValue(true)
        logi(params.requestedLoadSize.toString())
        compositeDisposable.add(githubService.getUsers(1, params.requestedLoadSize)
            .subscribe({
                isLoading.postValue(false)
                isRefresh.postValue(false)
                callback.onResult(it)
            }, {
                isLoading.postValue(false)
                isRefresh.postValue(false)
            }))
    }
    override fun loadAfter(params: LoadParams, callback: LoadCallback) {
        isLoading.postValue(true)
        compositeDisposable.add(
            githubService.getUsers(params.key, params.requestedLoadSize)
                .subscribe({ users ->
                    isLoading.postValue(false)
                    callback.onResult(users)
                }, {
                    isLoading.postValue(false)
                }))
    }
    override fun loadBefore(params: LoadParams, callback: LoadCallback) {
    }
    override fun getKey(item: User): Long {
        return item.id
    }
}

4. Tạo UsersDataSourceFactory

Dùng để khởi tạo DataSource cung cấp dữ liệu cho PagedList

class UsersDataSourceFactory(private val githubService: GithubService,
    private val compositeDisposable: CompositeDisposable) : DataSource.Factory() {

    val userDataSourceLiveData = MutableLiveData()

    override fun create(): DataSource {
        val userDataSource = UsersDataSource(githubService, compositeDisposable)
        userDataSourceLiveData.postValue(userDataSource)
        return userDataSource
    }
}

5. Tạo UserAdapter

Như mọi List thì phải có adapter để show data lên UI

class UserAdapter(
    private val retryCallback: () -> Unit) : PagedListAdapter(
    UserDiffCallback) {

    private var isLoading = false

    override fun onCreateViewHolder(p0: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            R.layout.item_paging_user -> UserItemViewHolder.create(p0)
            R.layout.item_paging_loading -> NetworkStateItemViewHolder.create(p0)
            else -> throw IllegalAccessException("unknown view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            R.layout.item_paging_user -> (holder as? UserItemViewHolder)?.bindTo(getItem(position))
            R.layout.item_paging_loading -> (holder as? NetworkStateItemViewHolder)?.bindTo(
                isLoading)
        }
    }

    override fun getItemViewType(position: Int): Int {
        return if (isLoading && position == itemCount - 1) {
            R.layout.item_paging_loading
        } else {
            R.layout.item_paging_user
        }
    }

    override fun getItemCount(): Int {
        return super.getItemCount() + if (isLoading) 1 else 0
    }

    fun setLoading(isLoading: Boolean?) {
        isLoading?.let {
            currentList?.isNotEmpty()?.let {
                val previousState = this.isLoading
                this.isLoading = isLoading
                if (previousState != isLoading) {
                    if (!isLoading) {
                        notifyItemRemoved(super.getItemCount())
                    } else {
                        notifyItemInserted(super.getItemCount())
                    }
                }
            }

            notifyItemInserted(super.getItemCount())
        }
    }

    companion object {
        val UserDiffCallback = object : DiffUtil.ItemCallback() {
            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
                return oldItem == newItem
            }

        }
    }
}

6. Tạo ViewModel

ViewModel sẽ chịu tạo PagedList và cung cấp cho hoạt động để nó có thể thay đổi dữ liệu mỗi khi request từ server.

class PagingViewModel : ViewModel() {

    var userList: LiveData>

    private val compositeDisposable = CompositeDisposable()

    private val pageSize = 5

    private val sourceFactory: UsersDataSourceFactory

    init {
        sourceFactory = UsersDataSourceFactory(GithubService.getService(), compositeDisposable)
        val config = PagedList.Config.Builder()
            .setPageSize(pageSize)
            .setInitialLoadSizeHint(pageSize * 2)
            .setEnablePlaceholders(false)
            .build()
        userList = LivePagedListBuilder(sourceFactory, config).build()
    }

    fun getLoading() = Transformations.switchMap(
        sourceFactory.userDataSourceLiveData) { it.isLoading }

    fun getRefreshState() = Transformations.switchMap(
        sourceFactory.userDataSourceLiveData) { it.isRefresh }
        
    fun reset() {
        sourceFactory.userDataSourceLiveData.value?.invalidate()
    }
    
    override fun onCleared() {
        super.onCleared()
        compositeDisposable.dispose()
    }

}

7. Tạo RecyclerView

  userAdapter = UserAdapter()
  usersRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL,
            false)
  usersRecyclerView.adapter = userAdapter 

Kết quả

Toàn bộ source code của mình ở đây

Cảm ơn các bạn đã đọc bài viết.

Chào thân ái và quyết thắng!

Bài viết gốc: https://viblo.asia/p/paging-library-trong-android-bJzKmggrl9N

admin

Leave a Reply

Your email address will not be published. Required fields are marked *