paging3
effect video
brief description
This Demo is completed using Hilt+Retrofit+Paging3, mainly to demonstrate the use of paging3 paging function. The following are the relevant dependencies required by the Demo
//retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
//paging
implementation 'androidx.paging:paging-runtime:3.1.1'
implementation 'androidx.paging:paging-compose:1.0.0-alpha14'
//Dagger - Hilt
implementation("com.google.dagger:hilt-android:2.44")
kapt("com.google.dagger:hilt-android-compiler:2.44")
// Compose dependencies
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
Hilt+Retrofit
access interface
Define the interface that needs to be accessed. This interface is Github api, and suspend
the field is used to prompt subsequent references. This content needs to be used in the coroutine
interface GithubService {
@GET("search/repositories?sort=stars&q=Android")
suspend fun queryGithubAsync(@Query("per_page")number:Int, @Query("page") page:Int):DetailsBean
}
network instance
UseCase
Three examples are provided, and the final example that needs to be referenced externally . The specific Hilt
dependency injection will not be explained here. Those who are interested can refer to Hilt dependency injection
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
const val BASE_URL:String = "https://api.github.com/"
@Singleton
@Provides
fun providerRetrofit():Retrofit{
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Singleton
@Provides
fun providerGithubService(retrofit: Retrofit): GithubService {
return retrofit.create(GithubService::class.java)
}
@Singleton
@Provides
fun providerUseCase(service: GithubService):UseCase{
return UseCase(GetProjects(service))
}
}
In the example provided by Hilt, UseCase
the task of accessing the network interface is implemented in
data class UseCase(
val getProjects: GetProjects
)
class GetProjects(private val service: GithubService) {
suspend operator fun invoke(number:Int,page:Int): DetailsBean {
return service.queryGithubAsync(number, page)
}
}
PagingSource
Our main implementation load
method; where is page
the number of current content pages, pageSize
the number of content that needs to be loaded on each page (can be defined externally), repository
is the obtained network data entity, and previousPage
is the previous page. A judgment is made here. If it is the first When there is one page, return null, otherwise, slide to the previous page; nextPage
for the next page, LoadResult.Page
load the required content for paging; LoadResult.Error
exceptions can be caught
class DataPagingSource(private val useCase: UseCase):PagingSource<Int,DetailBean>() {
override fun getRefreshKey(state: PagingState<Int, DetailBean>): Int? = null
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DetailBean> {
return try {
val page = params.key ?: 1 //当前页,默认第一页
val pageSize = params.loadSize //每页数据条数
val repository = useCase.getProjects(page,pageSize) //获取的数据源
val repositoryItem = repository.beans //获取的数据列表
val previousPage = if (page > 1) page - 1 else null //前一页
val nextPage = if (repositoryItem.isNotEmpty()) page+1 else null //下一页
Log.d("hiltViewModel","page=$page size=$pageSize")
LoadResult.Page(repositoryItem,previousPage,nextPage)
}catch (e:Exception){
LoadResult.Error(e)
}
}
}
ViewModel
Call the instance constructed by Hilt in the constructor; the getData
method is to obtain the paged data, and the return is Flow<PagingData<DetailBean>>
the type, where Flow<PagingData<...>>
the external is a fixed writing method, and the internal can be defined according to the needs. Then PagingConfig
in the configuration, we need to configure pageSize
and initialLoadSize
, if the latter is not defined , then the amount of content per page will be pageSize
tripled, and then add what we created above PagingSource
; finally converted into a stream, and then placed in a coroutine, which caches PagingData so that any downstream collection of this stream will share the same data
@HiltViewModel
class HomeViewModel @Inject constructor(private val useCase: UseCase):ViewModel() {
val PAGE_SIZE = 10
fun getData():Flow<PagingData<DetailBean>>{
return Pager(
config = PagingConfig(pageSize = PAGE_SIZE, initialLoadSize = PAGE_SIZE),
pagingSourceFactory = {
DataPagingSource(useCase) }
).flow.cachedIn(viewModelScope)
}
}
View
Get the data in the ViewModel
val datas = viewModel.getData().collectAsLazyPagingItems()
At the same time, if you need to add bottom refresh status bar, data error and other signs, you need to monitor loadState
, and the status is divided into five types:
- refresh: Triggered by loading data for the first time
- prepend: Triggered by sliding the previous page
- append: Triggered by sliding the next page
- source: corresponds to loading in [PagingSource]
- mediator: corresponding to the loading from [RemoteMediator]
We mainly userefresh
and hereappend
;
among them,refresh
listen in, if the data is null, then display a full-screen error prompt, here is the first time to load data;
then,append
listen inloading
AndError
two states, in which itloading
shows the loading state at the bottom,Error
and displays the error prompt at the bottom in , which is different fromrefresh
theError
state here, because there is data, there is no need to display a full-screen error prompt, just display the error status bar at the bottom of the data list
@Composable
fun GithubList(viewModel: HomeViewModel = hiltViewModel()){
val datas = viewModel.getData().collectAsLazyPagingItems()
LazyColumn(
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.background(grey)
.fillMaxSize()
.padding(10.dp)
){
when(datas.loadState.refresh){
is LoadState.Loading-> {
item {
loading() }}
is LoadState.Error-> {
if (datas.itemCount <= 0){
item{
/**
* 全屏显示错误*/
failedScreen() {
datas.retry()
}
}
}
}
}
itemsIndexed(datas){
_, value ->
if (value != null){
GithubItem(value)
}else{
empty {
datas.retry()
}
}
}
when(datas.loadState.append){
is LoadState.NotLoading-> {
}
is LoadState.Loading-> {
item {
loading()
}
}
is LoadState.Error-> {
if (datas.itemCount > 0){
/**
* 底部显示加载错误*/
item {
failed(){
datas.retry()} }
}
}
}
}
}