[Android] Tìm hiểu Paging Library

Đăng bởi: Admin | Lượt xem: 1929 | Chuyên mục: Android

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<List<User>>

    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<Long, User>() {

    val isLoading = MutableLiveData<Boolean>()
    val isRefresh = MutableLiveData<Boolean>()

    override fun loadInitial(params: LoadInitialParams<Long>, callback: LoadInitialCallback<User>) {
        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<Long>, callback: LoadCallback<User>) {
        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<Long>, callback: LoadCallback<User>) {
    }
    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<Long, User>() {

    val userDataSourceLiveData = MutableLiveData<UsersDataSource>()

    override fun create(): DataSource<Long, User> {
        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<User, RecyclerView.ViewHolder>(
    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<User>() {
            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<PagedList<User>>

    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<Long, User>(sourceFactory, config).build()
    }

    fun getLoading() = Transformations.switchMap<UsersDataSource, Boolean>(
        sourceFactory.userDataSourceLiveData) { it.isLoading }

    fun getRefreshState() = Transformations.switchMap<UsersDataSource, Boolean>(
        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

vncoder logo

Theo dõi VnCoder trên Facebook, để cập nhật những bài viết, tin tức và khoá học mới nhất!



Khóa học liên quan

Khóa học: Android

Học Kotlin cơ bản
Số bài học:
Lượt xem: 17611
Đăng bởi: Admin
Chuyên mục: Android

Học lập trình Flutter cơ bản
Số bài học:
Lượt xem: 58512
Đăng bởi: Admin
Chuyên mục: Android

Lập trình Android cơ bản
Số bài học:
Lượt xem: 22990
Đăng bởi: Admin
Chuyên mục: Android