📈【Jetpack】Android Paging3 分頁加載 範例🔃
Android Paging3 是一個新一代的 Android 內存管理庫,它的目的是提供一個安全可靠的方法,為 Android 數據庫和網絡數據提供分頁技術支持。
它提供了彈性的方法讓 UI 可以加載應用程序內容,無論是從數據庫或者網絡數據源加載,內存佔用也可以得到控制,來幫助需要處理大量數據的手機應用開發者提高產品的性能與響應。
Paging3 的最初的目標是處理昂貴的 SQL 查詢,處理數據庫內容,以及加載過多會導致 OutOfMemory 保護的網絡數據。它可以支持同步和異步的數據加載,並且同時兼容大部分的 Android 庫(比如 Room 數據庫)。
文章目錄
- Paging3 & Retrofit & Fragment KTX 導入
- Paging3 Response Model
- Paging3 Retrofit & Service
- Paging3 PagingSource
- Paging3 Repository
- Paging3 ViewModel
- Paging3 Adapter
- Paging3 FooterAdapter
- Paging3
- Developer Documents Paging3
1.Paging3 & Retrofit & Fragment KTX 導入
build.gradle
android {
buildFeatures {
dataBinding true
}
}
dependencies {
def fragment_version = "1.5.5"
implementation "androidx.fragment:fragment-ktx:fragment_version"
implementation 'androidx.paging:paging-runtime:3.2.0-alpha03'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
def paging_version = "3.1.1"
implementation "androidx.paging:paging-runtime:paging_version"
}
2.Paging3 Response Model
RepoRec.kt
data class RepoRec(
@SerializedName("items") val items: List<Repo>
)
data class Repo(
@SerializedName("id") val id: Int,
@SerializedName("name") val name: String,
@SerializedName("description") val description: String,
@SerializedName("stargazers_count") val starCount: Int
)
3.Paging3 Retrofit & Service
RetrofitUtils.kt
class RetrofitUtils {
companion object {
val instance: RetrofitUtils by lazy { RetrofitUtils() }
}
fun <T> getService(clazz: Class<T>): T {
return retrofit.create(clazz)
}
private val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://api.github.com/")
.build()
}
GitHubService.kt
interface GitHubService {
@GET("search/repositories?sort=stars&q=Android")
suspend fun searchRepositories(@Query("page") page: Int, @Query("per_page") perPage: Int) : RepoRec
}
4.Paging3 PagingSource
RepoPagingSource.kt
class RepoPagingSource(private val gitHubService: GitHubService): PagingSource<Int, Repo>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
return try {
val page = params.key ?: 1
val pageSize = params.loadSize
val repoRec = gitHubService.searchRepositories(page, pageSize)
val repoItems = repoRec.items
val prevKey = if (page > 1) page - 1 else null
val nextKey = if (repoItems.isNotEmpty()) page + 1 else null
LoadResult.Page(repoItems, prevKey, nextKey)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, Repo>): Int? = null
}
5.Paging3 Repository
Repository.kt
object Repository {
private const val PAGE_SIZE = 50
private val gitHubService = RetrofitUtils.instance.getService(GitHubService::class.java)
fun getPagingData(): Flow<PagingData<Repo>> = Pager(
config = PagingConfig(PAGE_SIZE),
pagingSourceFactory = { RepoPagingSource(gitHubService) }
).flow
}
6.Paging3 ViewModel
MainViewModel.kt
class MainViewModel : ViewModel() {
fun getPagingData(): Flow<PagingData<Repo>> {
return Repository.getPagingData().cachedIn(viewModelScope)
}
}
7.Paging3 Adapter
repo_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="repo"
type="com.example.jetpackdemo.Repo" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:orientation="vertical">
<TextView
android:text="@{repo.name}"
android:id="@+id/name_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:maxLines="1"
android:ellipsize="end"
android:textColor="#5194fd"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:text="@{repo.description}"
android:id="@+id/description_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:maxLines="10"
android:ellipsize="end" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="end"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="5dp"
android:src="@drawable/ic_star"
tools:ignore="ContentDescription" />
<TextView
android:text="@{Integer.toString(repo.starCount)}"
android:id="@+id/star_count_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical" />
</LinearLayout>
</LinearLayout>
</layout>
RepoAdapter.kt
class RepoAdapter : PagingDataAdapter<Repo, RepoAdapter.RepoViewHolder>(COMPARATOR) {
companion object {
private val COMPARATOR = object : DiffUtil.ItemCallback<Repo>() {
override fun areItemsTheSame(oldItem: Repo, newItem: Repo): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Repo, newItem: Repo): Boolean {
return oldItem == newItem
}
}
}
override fun onBindViewHolder(holder: RepoViewHolder, position: Int) {
val repo = getItem(position)
holder.bind(repo)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
RepoViewHolder(RepoItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
class RepoViewHolder(private val binding: RepoItemBinding) : ViewHolder(binding.root) {
fun bind(repo: Repo?) {
binding.repo = repo
binding.executePendingBindings()
}
}
}
8.Paging3 FooterAdapter
footer_item.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<Button
android:id="@+id/retry_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="Retry" />
</FrameLayout>
</layout>
FooterAdapter.kt
class FooterAdapter(private val retry: () -> Unit) : LoadStateAdapter<FooterAdapter.FooterViewHolder>() {
override fun onBindViewHolder(holder: FooterViewHolder, loadState: LoadState) {
holder.bind(loadState, retry)
}
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): FooterViewHolder {
return FooterViewHolder(FooterItemBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
)
}
class FooterViewHolder(private val binding: FooterItemBinding) : ViewHolder(binding.root) {
fun bind(loadState: LoadState, retry: () -> Unit) {
binding.retryButton.setOnClickListener {
retry()
}
binding.progressBar.isVisible = loadState is LoadState.Loading
binding.retryButton.isVisible = loadState is LoadState.Error
}
}
}
9.Paging3
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
</layout>
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: MainViewModel by viewModels()
private val repoAdapter = RepoAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(this@MainActivity)
adapter = repoAdapter.withLoadStateFooter(FooterAdapter { repoAdapter.retry() })
}
lifecycleScope.launch {
viewModel.getPagingData().collect { pagingData ->
repoAdapter.submitData(pagingData)
}
}
repoAdapter.addLoadStateListener {
when (it.refresh) {
is LoadState.NotLoading -> {
binding.progressBar.visibility = View.INVISIBLE
binding.recyclerView.visibility = View.VISIBLE
}
is LoadState.Loading -> {
binding.progressBar.visibility = View.VISIBLE
binding.recyclerView.visibility = View.INVISIBLE
}
is LoadState.Error -> {
val state = it.refresh as LoadState.Error
binding.progressBar.visibility = View.INVISIBLE
Toast.makeText(this, "Load Error: ${state.error.message}", Toast.LENGTH_SHORT)
.show()
}
}
}
}
}
10.Developer Documents Paging3
Open in Documents Paging3