Skip to content

Commit 9ead496

Browse files
committed
接入Paging3改写Paging2繁琐的分页代码
1 parent a05e270 commit 9ead496

File tree

14 files changed

+171
-173
lines changed

14 files changed

+171
-173
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ Model-View-ViewModel,View 指绿色的 Activity/Fragment,主要负责界面
8484
Glide相比起Fresco要轻量很多,api调用起来也很简洁,对图片加载要求不是很高的话建议使用Glide。
8585

8686
# 更新日志
87+
### v2.4
88+
* 接入Paging3改写Paging2繁琐的分页代码,Paging3提供了刷新、重试等惊喜功能
8789
### v2.3
8890
* 新增OAuth2授权登录方式,旧版登录方式官方不再推荐(官方2020/11后废弃旧版登录方式)
8991
* 调整项目结构,优化代码

app/build.gradle

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ android {
1313
applicationId "com.fmt.github"
1414
minSdkVersion 21
1515
targetSdkVersion 29
16-
versionCode 11
17-
versionName "2.3"
16+
versionCode 12
17+
versionName "2.4"
1818
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1919
}
2020
signingConfigs {
@@ -44,6 +44,9 @@ android {
4444
sourceCompatibility 1.8
4545
targetCompatibility 1.8
4646
}
47+
kotlinOptions{
48+
jvmTarget = "1.8"
49+
}
4750
}
4851

4952
dependencies {
@@ -59,8 +62,8 @@ dependencies {
5962
implementation 'com.google.android.material:material:1.1.0'
6063

6164
//协程
62-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3'
63-
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
65+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6'
66+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6'
6467

6568
//Android KTX 是一组 Kotlin 扩展程序,属于 Android Jetpack 系列
6669
implementation 'androidx.core:core-ktx:1.3.0'
@@ -71,20 +74,20 @@ dependencies {
7174

7275
//okhttp + retrofit(2.6.0以后支持协程)
7376
implementation("com.squareup.okhttp3:logging-interceptor:4.0.0")
74-
implementation 'com.squareup.retrofit2:retrofit:2.7.1'
75-
implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
77+
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
78+
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
7679

7780
//协程 + room
7881
implementation "androidx.room:room-runtime:2.2.5"
7982
kapt "androidx.room:room-compiler:2.2.5"
8083
implementation "androidx.room:room-ktx:2.2.5"
8184

8285
//navigation
83-
implementation "androidx.navigation:navigation-fragment-ktx:2.3.0-rc01"
84-
implementation "androidx.navigation:navigation-ui-ktx:2.3.0-rc01"
86+
implementation "androidx.navigation:navigation-fragment-ktx:2.3.0"
87+
implementation "androidx.navigation:navigation-ui-ktx:2.3.0"
8588

8689
//paging
87-
implementation "androidx.paging:paging-runtime-ktx:2.1.2"
90+
implementation "androidx.paging:paging-runtime:3.0.0-alpha02"
8891

8992
//WorkManager
9093
implementation "androidx.work:work-runtime-ktx:2.3.4"
@@ -116,7 +119,7 @@ dependencies {
116119
implementation 'com.github.chrisbanes:PhotoView:2.3.0@aar'
117120

118121
//bugly捕获异常
119-
implementation 'com.tencent.bugly:crashreport:3.2.1'
122+
implementation 'com.tencent.bugly:crashreport:3.2.3'
120123
implementation project(path: ':LaunchStarter')
121124

122125
}
Lines changed: 27 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,59 @@
11
package com.fmt.github.base.fragment
22

33
import androidx.lifecycle.Observer
4-
import androidx.paging.PagedList
5-
import androidx.paging.PagedListAdapter
4+
import androidx.paging.ExperimentalPagingApi
5+
import androidx.paging.LoadState
6+
import androidx.paging.PagingDataAdapter
67
import androidx.recyclerview.widget.LinearLayoutManager
78
import androidx.recyclerview.widget.RecyclerView
89
import com.fmt.github.R
910
import com.fmt.github.base.viewmodel.BaseLPagingViewModel
10-
import com.fmt.github.ext.yes
11-
import com.google.android.material.button.MaterialButton
12-
import com.kennyc.view.MultiStateView
13-
import com.scwang.smartrefresh.layout.api.RefreshLayout
14-
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener
15-
import com.scwang.smartrefresh.layout.listener.OnRefreshListener
16-
import kotlinx.android.synthetic.main.common_refresh_recyclerview.*
11+
import com.fmt.github.home.adapter.PostsLoadStateAdapter
12+
import kotlinx.android.synthetic.main.common_recyclerview.*
1713

1814
/**
1915
* 基于Paging封装通用分页列表
2016
*/
21-
abstract class BasePagingVMFragment<M, VM : BaseLPagingViewModel<M>, VH : RecyclerView.ViewHolder> :
22-
BaseVMFragment(), OnRefreshListener,
23-
OnLoadMoreListener {
17+
abstract class BasePagingVMFragment<M : Any, VM : BaseLPagingViewModel<M>, VH : RecyclerView.ViewHolder> :
18+
BaseVMFragment() {
2419

25-
private val mAdapter: PagedListAdapter<M, VH> by lazy { getAdapter() }
20+
private val mAdapter: PagingDataAdapter<M, VH> by lazy { getAdapter() }
2621

2722
lateinit var mViewModel: VM
2823

29-
override fun getLayoutRes(): Int = R.layout.common_refresh_recyclerview
24+
override fun getLayoutRes(): Int = R.layout.common_recyclerview
3025

26+
@ExperimentalPagingApi
3127
override fun initView() {
32-
mMultipleStatusView.viewState = MultiStateView.ViewState.LOADING
33-
mMultipleStatusView.getView(MultiStateView.ViewState.ERROR)
34-
?.findViewById<MaterialButton>(R.id.mb_retry_load)?.setOnClickListener {
35-
mMultipleStatusView.viewState = MultiStateView.ViewState.LOADING
36-
mViewModel.refresh()
37-
}
38-
mRefreshLayout.run {
39-
setOnRefreshListener(this@BasePagingVMFragment)
40-
setOnLoadMoreListener(this@BasePagingVMFragment)
28+
mSwipeRefreshLayout.setOnRefreshListener {
29+
mAdapter.refresh()
4130
}
42-
31+
mRecyclerView.adapter =
32+
mAdapter.withLoadStateFooter(PostsLoadStateAdapter { mAdapter.retry() })
4333
mRecyclerView.layoutManager = LinearLayoutManager(mActivity)
44-
mRecyclerView.adapter = mAdapter
45-
46-
mViewModel = getViewModel() as VM
47-
mViewModel.mBoundaryData.observe(this, Observer {
48-
it.yes {
49-
mMultipleStatusView.viewState = MultiStateView.ViewState.CONTENT
34+
mAdapter.addLoadStateListener {
35+
when (it.refresh) {
36+
is LoadState.Loading -> mSwipeRefreshLayout.isRefreshing = true
37+
is LoadState.NotLoading -> mSwipeRefreshLayout.isRefreshing = false
38+
is LoadState.Error -> mSwipeRefreshLayout.isRefreshing = false
5039
}
51-
})
52-
mViewModel.refreshState.observe(this, Observer {
53-
it.yes {
54-
mMultipleStatusView.viewState = MultiStateView.ViewState.ERROR
55-
}
56-
})
57-
mViewModel.loadMoreState.observe(this, Observer {
58-
//上拉加载进度条只有在Paging加载更多失败时才有效(用于规避Paging加载更多失败后,无法再次加载问题)
59-
mRefreshLayout.setEnableLoadMore(it)
60-
})
40+
}
41+
mAdapter.addDataRefreshListener {
42+
mSwipeRefreshLayout.isRefreshing = false
43+
}
6144

45+
mViewModel = getViewModel() as VM
6246
afterViewCreated()
6347
}
6448

6549
override fun initData() {
66-
mViewModel.pagedList.observe(this, Observer<PagedList<M>> {
67-
mAdapter.submitList(it)
50+
mViewModel.pagedList.observe(this, Observer {
51+
mAdapter.submitData(lifecycle, it)
6852
})
6953
}
7054

71-
override fun onRefresh(refreshLayout: RefreshLayout) {
72-
mViewModel.refresh()
73-
}
74-
75-
override fun onLoadMore(refreshLayout: RefreshLayout) {
76-
mViewModel.loadMoreRetry()
77-
}
78-
79-
override fun dismissLoading() {
80-
mRefreshLayout.run {
81-
finishRefresh()
82-
finishLoadMore()
83-
}
84-
}
85-
8655
abstract fun afterViewCreated()
8756

88-
abstract fun getAdapter(): PagedListAdapter<M, VH>
57+
abstract fun getAdapter(): PagingDataAdapter<M, VH>
8958

9059
}
Lines changed: 20 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,39 @@
11
package com.fmt.github.base.viewmodel
22

3-
import androidx.lifecycle.LiveData
4-
import androidx.lifecycle.MutableLiveData
3+
import androidx.lifecycle.asLiveData
54
import androidx.lifecycle.viewModelScope
6-
import androidx.paging.DataSource
7-
import androidx.paging.LivePagedListBuilder
8-
import androidx.paging.PageKeyedDataSource
9-
import androidx.paging.PagedList
5+
import androidx.paging.*
106
import com.fmt.github.config.Configs
11-
import kotlinx.coroutines.launch
127

138
/**
149
* 基于Paging封装通用ViewModel
1510
*/
16-
abstract class BaseLPagingViewModel<M> : BaseViewModel() {
1711

18-
private lateinit var mDataSource: PageKeyedDataSource<Int, M>
12+
abstract class BaseLPagingViewModel<M : Any> : BaseViewModel() {
1913

20-
val mBoundaryData = MutableLiveData(false)//控制页面显示状态
21-
22-
val refreshState = MutableLiveData(false)
23-
24-
val loadMoreState = MutableLiveData(false)
25-
26-
private var loadMoreRetry: (() -> Unit)? = null
27-
28-
val pagedList: LiveData<PagedList<M>> by lazy {
29-
LivePagedListBuilder<Int, M>(
30-
object : DataSource.Factory<Int, M>() {
31-
override fun create(): DataSource<Int, M> {
32-
mDataSource = PageDataSource()
33-
return mDataSource
34-
}
35-
}, PagedList.Config.Builder()
36-
.setPageSize(Configs.PAGE_SIZE)
37-
.setInitialLoadSizeHint(12)
38-
.build()
39-
).build()
14+
val pagedList by lazy {
15+
Pager(config = PagingConfig(pageSize = Configs.PAGE_SIZE, prefetchDistance = 1)) {
16+
PageDataSource()
17+
}.flow.asLiveData().cachedIn(viewModelScope)
4018
}
4119

42-
//真正加载数据的来源
43-
inner class PageDataSource : PageKeyedDataSource<Int, M>() {
44-
override fun loadInitial(
45-
params: LoadInitialParams<Int>,
46-
callback: LoadInitialCallback<Int, M>
47-
) {
48-
viewModelScope.launch {
49-
try {
50-
val list = getDataList(1)
51-
mBoundaryData.postValue(list.isNotEmpty())
52-
callback.onResult(list, null, 2)
53-
mStateLiveData.value = SuccessState
54-
refreshState.postValue(false)
55-
} catch (e: Exception) {
56-
refreshState.postValue(true)
57-
mStateLiveData.value = ErrorState(e.message)
58-
}
20+
inner class PageDataSource : PagingSource<Int, M>() {
21+
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, M> {
22+
return try {
23+
val page = params.key ?: 1
24+
val list = getDataList(page)
25+
LoadResult.Page(
26+
data = list,
27+
prevKey = null,
28+
nextKey = if (list.isEmpty()) null else page + 1
29+
)
30+
} catch (e: Exception) {
31+
mStateLiveData.value = ErrorState(e.message)
32+
LoadResult.Error(e)
5933
}
6034
}
61-
62-
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, M>) {
63-
viewModelScope.launch {
64-
try {
65-
val list = getDataList(params.key)
66-
callback.onResult(
67-
list,
68-
params.key + 1
69-
)
70-
mStateLiveData.value = SuccessState
71-
loadMoreState.postValue(false)
72-
} catch (e: Exception) {
73-
mStateLiveData.value = ErrorState(e.message)
74-
loadMoreState.postValue(true)
75-
loadMoreRetry = {
76-
//保存加载更多失败时的场景,防止第一次加载失败后,后续无法再次调用loadAfter
77-
loadMoreFail(params, callback)
78-
}
79-
}
80-
}
81-
}
82-
83-
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, M>) {}
84-
}
85-
86-
fun refresh() {//Paging刷新数据
87-
mDataSource.invalidate()
88-
}
89-
90-
fun loadMoreRetry() {//加载更多失败重试
91-
loadMoreRetry?.invoke()
92-
}
93-
94-
private fun loadMoreFail(//加载更多失败时调用
95-
params: PageKeyedDataSource.LoadParams<Int>,
96-
callback: PageKeyedDataSource.LoadCallback<Int, M>
97-
) {
98-
mDataSource.loadAfter(params, callback)
9935
}
10036

10137
abstract suspend fun getDataList(page: Int): List<M>
10238

103-
10439
}

app/src/main/java/com/fmt/github/config/Configs.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ object Configs {
2727
const val BUGLY_APP_ID = "8a37e3b7c7"
2828

2929
//分页数量
30-
const val PAGE_SIZE = 10
30+
const val PAGE_SIZE = 20
3131
}

app/src/main/java/com/fmt/github/ext/BooleanExt.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ inline fun <T> Boolean.yes(block: () -> T): BooleanExt<T> =//inline提升性能
1414
else -> OtherWise
1515
}
1616

17-
fun <T> Boolean.no(block: () -> T): BooleanExt<T> = when {
17+
inline fun <T> Boolean.no(block: () -> T): BooleanExt<T> = when {
1818
this -> OtherWise
1919
else -> Success(block())
2020
}

app/src/main/java/com/fmt/github/home/adapter/HomeAdapter.kt

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import android.app.Activity
44
import android.view.LayoutInflater
55
import android.view.View
66
import android.view.ViewGroup
7-
import androidx.paging.PagedListAdapter
7+
import androidx.paging.PagingDataAdapter
88
import androidx.recyclerview.widget.DiffUtil
99
import androidx.recyclerview.widget.RecyclerView
1010
import com.fmt.github.config.Configs
@@ -15,17 +15,19 @@ import com.fmt.github.user.activity.go2UserInfoActivity
1515
import com.fmt.github.user.model.UserModel
1616

1717
class HomeAdapter(private val mContext: Activity) :
18-
PagedListAdapter<ReceivedEventModel, HomeAdapter.ViewHolder>(object :
19-
DiffUtil.ItemCallback<ReceivedEventModel>() {
20-
override fun areItemsTheSame(oldItem: ReceivedEventModel, newItem: ReceivedEventModel) =
21-
oldItem.id == newItem.id
22-
23-
override fun areContentsTheSame(
24-
oldItem: ReceivedEventModel,
25-
newItem: ReceivedEventModel
26-
) =
27-
oldItem == newItem
28-
}) {
18+
PagingDataAdapter<ReceivedEventModel, HomeAdapter.ViewHolder>(DIFF_CALLBACK) {
19+
20+
companion object {
21+
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<ReceivedEventModel>() {
22+
override fun areItemsTheSame(oldItem: ReceivedEventModel, newItem: ReceivedEventModel) =
23+
oldItem.id == newItem.id
24+
25+
override fun areContentsTheSame(
26+
oldItem: ReceivedEventModel,
27+
newItem: ReceivedEventModel
28+
) = oldItem == newItem
29+
}
30+
}
2931

3032
private val mLayoutInflater: LayoutInflater = LayoutInflater.from(mContext)
3133

@@ -39,8 +41,10 @@ class HomeAdapter(private val mContext: Activity) :
3941
holder.bindData(receivedEventModel)
4042
holder.itemView.setOnClickListener {
4143
val splitArr = receivedEventModel.repo.name.split("/")
42-
go2ReposDetailActivity(mContext,"${Configs.GITHUB_BASE_URL}${receivedEventModel.repo.name}",
43-
splitArr[1],splitArr[0])
44+
go2ReposDetailActivity(
45+
mContext, "${Configs.GITHUB_BASE_URL}${receivedEventModel.repo.name}",
46+
splitArr[1], splitArr[0]
47+
)
4448
}
4549
}
4650
}

0 commit comments

Comments
 (0)