PagingSource และ Pager
กำหนดวิธีโหลดแต่ละหน้า
PagingSource และ Pager เป็นบทเรียน Android Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Android Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
กำหนดวิธีโหลดหน้า
หากต้องการแบ่งหน้าข้อมูลจาก API เครือข่าย คุณต้องเขียน PagingSource ซึ่งจะตอบคำถามสองข้อให้ Paging 3:
- จะโหลดหน้าสำหรับคีย์ที่กำหนดได้อย่างไร
- หากผู้ใช้รีเฟรช ควรกลับมาโหลดต่อจากที่ใด
จากนั้นห่อหุ้มด้วย Pager เพื่อแปลงเป็น Flow<PagingData>
พารามิเตอร์ประเภทของ PagingSource
PagingSource<Key, Value> รับพารามิเตอร์ประเภทสองตัว:
- Key - ใช้ระบุหน้า สำหรับ API ที่ใช้หมายเลขหน้า จะเป็น
Intส่วน API ที่ใช้เคอร์เซอร์จะเป็นโทเค็นString - Value - ประเภทของรายการ เช่น
Article
import androidx.paging.PagingSource
import androidx.paging.PagingState
class ArticlePagingSource(
private val api: ArticleApi
) : PagingSource<Int, Article>() {
// implement load() and getRefreshKey()
}การใช้งาน load()
load() เป็นฟังก์ชัน suspend โดยรับ params.key (หน้าที่ต้องการดึงข้อมูล) และส่งคืน LoadResult
เมื่อสำเร็จ ให้ส่งคืน LoadResult.Page พร้อมรายการ รวมถึงคีย์ก่อนหน้าและคีย์ถัดไป หากคีย์เป็น null หมายความว่าไม่มีหน้าในทิศทางนั้น
override suspend fun load(
params: LoadParams<Int>
): LoadResult<Int, Article> {
val page = params.key ?: 1 // first load has a null key
return try {
val response = api.getArticles(page = page, size = params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.items.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}เหตุผลที่ prevKey และ nextKey สำคัญ
Paging ใช้ nextKey เพื่อโหลดข้อมูลไปข้างหน้าเมื่อผู้ใช้เลื่อนลง และใช้ prevKey เพื่อโหลดข้อมูลย้อนกลับ (มีประโยชน์เมื่อเริ่มต้นจากตรงกลางรายการ)
การส่งคืน null สำหรับ nextKey จะบอก Paging ว่าไม่มีหน้าถัดไปแล้ว จึงหยุดการขอข้อมูล หากลืมทำเช่นนี้ อาจทำให้เกิดคำขอว่างเปล่าไม่รู้จบ
// Stop forward paging when the server returns an empty page
nextKey = if (response.items.isEmpty()) null else page + 1
// Stop backward paging at the first page
prevKey = if (page == 1) null else page - 1การใช้งาน getRefreshKey()
เมื่อรายการรีเฟรช (โดยการดึงเพื่อรีเฟรชหรือการทำให้ข้อมูลใช้ไม่ได้) Paging จำเป็นต้องทราบว่าควรโหลดหน้าใดใหม่ เพื่อให้ผู้ใช้ยังอยู่ในตำแหน่งเดิมโดยประมาณ
getRefreshKey() ใช้ anchorPosition ปัจจุบัน ซึ่งคือตำแหน่งของรายการที่ใกล้กับพื้นที่แสดงผลที่สุด เพื่อเลือกคีย์ที่เหมาะสม
override fun getRefreshKey(state: PagingState<Int, Article>): Int? {
return state.anchorPosition?.let { anchor ->
val closestPage = state.closestPageToPosition(anchor)
closestPage?.prevKey?.plus(1)
?: closestPage?.nextKey?.minus(1)
}
}จัดการข้อผิดพลาดอย่างเหมาะสม
ครอบการเรียกเครือข่ายด้วย try/catch และส่งคืน LoadResult.Error(e) เมื่อเกิดข้อผิดพลาด Paging จะแสดงสถานะนี้ใน UI เป็น LoadState.Error เพื่อให้คุณแสดงปุ่มลองใหม่ได้
อย่าปล่อยให้ข้อยกเว้นหลุดออกจาก load() ให้ดักจับและแปลงเป็น LoadResult.Error เสมอ
return try {
val response = api.getArticles(page = page, size = params.loadSize)
LoadResult.Page(
data = response.items,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.items.isEmpty()) null else page + 1
)
} catch (e: IOException) { // no network
LoadResult.Error(e)
} catch (e: HttpException) { // non-2xx response
LoadResult.Error(e)
}การสร้าง Pager
Pager เชื่อม PagingConfig ของคุณเข้ากับโรงงานที่สร้าง PagingSource ใหม่ คุณสมบัติ .flow ของมันคือ Flow<PagingData> ที่ UI จะรวบรวม
แลมบ์ดาของโรงงานต้องสร้างแหล่งข้อมูลใหม่ทุกครั้ง เพราะ Paging จะทำให้แหล่งข้อมูลใช้ไม่ได้และสร้างขึ้นใหม่เมื่อรีเฟรช
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow
class ArticleRepository(private val api: ArticleApi) {
fun articleStream(): Flow<PagingData<Article>> = Pager(
config = PagingConfig(pageSize = 20, prefetchDistance = 5),
pagingSourceFactory = { ArticlePagingSource(api) }
).flow
}cachedIn สำหรับ ViewModels
การรวบรวม PagingData เป็นการดำเนินการแบบครั้งเดียว การรวบรวมซ้ำจะเริ่มโหลดใหม่ เพื่อให้ทำงานต่อได้เมื่อการกำหนดค่าเปลี่ยนแปลง และให้ผู้รวบรวมหลายรายใช้ข้อมูลร่วมกันได้ ให้แคชโฟลว์ใน viewModelScope ด้วย cachedIn
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.cachedIn
class ArticleViewModel(
repo: ArticleRepository
) : ViewModel() {
val articles = repo.articleStream()
.cachedIn(viewModelScope)
}loadSize เทียบกับ pageSize
โปรดสังเกตว่า load() อ่านค่า params.loadSize ไม่ใช่ pageSize ที่คุณกำหนดค่าไว้โดยตรง
ในการโหลดครั้งแรก Paging อาจขอข้อมูลก้อนแรกที่มีขนาดใหญ่กว่า (ควบคุมโดย initialLoadSize ใน PagingConfig ซึ่งค่าเริ่มต้นคือ 3 เท่าของขนาดหน้า) ให้ส่ง params.loadSize ไปยัง API เสมอ เพื่อให้คำขอตรงกับขนาดที่ Paging คาดว่าจะได้รับ
// Correct: respect the size Paging asks for
val response = api.getArticles(page = page, size = params.loadSize)
// PagingConfig can tune the first load:
PagingConfig(pageSize = 20, initialLoadSize = 40)API แบบใช้เคอร์เซอร์
ไม่ใช่ทุก API ที่ใช้หมายเลขหน้า บาง API จะส่งคืนเคอร์เซอร์หรือโทเค็นที่ชี้ไปยังหน้าถัดไป รูปแบบการทำงานเหมือนเดิม เพียงเปลี่ยนประเภท Key เป็น String และใช้โทเค็นจากการตอบกลับ
class CursorArticleSource(
private val api: ArticleApi
) : PagingSource<String, Article>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Article> {
val cursor = params.key // null on first load
return try {
val res = api.getArticles(cursor = cursor, size = params.loadSize)
LoadResult.Page(
data = res.items,
prevKey = null, // forward-only cursor
nextKey = res.nextCursor // null when exhausted
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<String, Article>) = null
}นำทุกส่วนมาประกอบกัน
ตอนนี้คุณมีชั้นข้อมูลครบถ้วนแล้ว ได้แก่ PagingSource ที่โหลดทีละหน้า Pager ที่สตรีมหน้าเหล่านั้น และ ViewModel ที่แคชโฟลว์
ชั้น UI เพียงรวบรวม viewModel.articles ซึ่งเราจะแสดงผลในบทเรียนถัดไปด้วย Compose LazyColumn
// Data layer summary
// 1. ArticlePagingSource : PagingSource<Int, Article>
// 2. Pager(config, factory).flow -> Flow<PagingData<Article>>
// 3. ViewModel: repo.articleStream().cachedIn(viewModelScope)
// UI just collects viewModel.articlesตรวจสอบความเข้าใจ
ใน PagingSource ที่ใช้หมายเลขหน้า การส่งคืน nextKey = null จาก load() สื่อถึงอะไร
สรุป: PagingSource และ Pager
คุณได้สร้างชั้นข้อมูลสำหรับการแบ่งหน้าแล้ว:
PagingSource<Key, Value>ใช้งานload()และgetRefreshKey()load()ส่งคืนLoadResult.Pageพร้อมprevKey/nextKeyหรือLoadResult.Error- คีย์ที่เป็น
nullจะหยุดการแบ่งหน้าในทิศทางนั้น Pager(config, factory).flowสร้างFlow<PagingData>cachedIn(viewModelScope)ช่วยเก็บข้อมูลไว้เมื่อการกำหนดค่าเปลี่ยนแปลง
ถัดไป: การแสดงผลโฟลว์นี้ในรายการ Compose
คำถามที่พบบ่อย
บทเรียน “PagingSource และ Pager” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “PagingSource และ Pager” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Android Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Android Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “PagingSource และ Pager”
กำหนดวิธีโหลดแต่ละหน้า คุณปฏิบัติ Android Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Android Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน Android Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “PagingSource และ Pager” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน Android Academy นี้ได้ไหม
ได้ บทเรียน Android Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- เหตุใดจึงต้องแบ่งหน้า
- PagingSource และ Pager
- การแบ่งหน้าในรายการ Compose
- RemoteMediator และการแคช