โหลดและแสดงข้อมูลการแบ่งหน้า

ไลบรารีการแบ่งหน้ามีความสามารถอันทรงพลังในการโหลดและแสดง ข้อมูลที่แบ่งหน้าจากชุดข้อมูลขนาดใหญ่ คู่มือนี้แสดงวิธีใช้ไลบรารี Paging เพื่อตั้งค่าสตรีมของข้อมูลที่แบ่งหน้าจากแหล่งข้อมูลเครือข่ายและแสดง ในรายการแบบ Lazy

กำหนดแหล่งข้อมูล

ขั้นตอนแรกคือการกําหนดการติดตั้งใช้งาน PagingSource เพื่อระบุแหล่งข้อมูล PagingSourceคลาส API มีเมธอด load ซึ่งคุณจะลบล้างเพื่อระบุวิธีเรียกข้อมูลที่แบ่งหน้าจากแหล่งข้อมูลที่เกี่ยวข้อง

ใช้คลาส PagingSource โดยตรงเพื่อใช้ Kotlin โครูทีน สำหรับการโหลดแบบไม่พร้อมกัน

เลือกประเภทคีย์และค่า

PagingSource<Key, Value> มีพารามิเตอร์ประเภท 2 รายการ ได้แก่ Key และ Value คีย์ กำหนดตัวระบุที่ใช้ในการโหลดข้อมูล และค่าคือประเภทของ ข้อมูลเอง เช่น หากคุณโหลดหน้าเว็บของUserออบเจ็กต์จากเครือข่าย โดยส่งIntหมายเลขหน้าไปยัง Retrofit ให้เลือก Int เป็นKeyประเภท และ User เป็นValueประเภท

กำหนด PagingSource

ตัวอย่างต่อไปนี้ใช้ PagingSource ที่โหลดหน้าของรายการตามหมายเลขหน้า ประเภท Key คือ Int และประเภท Value คือ User

class ExamplePagingSource(
    val backend: ExampleBackendService,
    val query: String
) : PagingSource<Int, User>() {
  override suspend fun load(
    params: LoadParams<Int>
  ): LoadResult<Int, User> {

    init {
        // the data source is expected to be immutable
        // invalidate PagingSource if data source
        // has updated
        backEnd.addDatabaseOnChangedListener {
            invalidate()
        }
    }

    try {
      // Start refresh at page 1 if undefined.
      val nextPageNumber = params.key ?: 1
      val response = backend.searchUsers(query, nextPageNumber)
      return LoadResult.Page(
        data = response.users,
        prevKey = null, // Only paging forward.
        nextKey = nextPageNumber + 1
      )
    } catch (e: Exception) {
      // Handle errors in this block and return LoadResult.Error for
      // expected errors (such as a network failure).
    }
  }

  override fun getRefreshKey(state: PagingState<Int, User>): Int? {
    // Try to find the page key of the closest page to anchorPosition from
    // either the prevKey or the nextKey; you need to handle nullability
    // here.
    //  * prevKey == null -> anchorPage is the first page.
    //  * nextKey == null -> anchorPage is the last page.
    //  * both prevKey and nextKey are null -> anchorPage is the
    //    initial page, so return null.
    return state.anchorPosition?.let { anchorPosition ->
      val anchorPage = state.closestPageToPosition(anchorPosition)
      anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
    }
  }
}

การติดตั้งใช้งาน PagingSource โดยทั่วไปจะส่งพารามิเตอร์ที่ระบุไว้ใน เครื่องมือสร้าง ไปยังเมธอด load เพื่อโหลดข้อมูลที่เหมาะสมสำหรับการค้นหา ใน ตัวอย่างข้างต้น พารามิเตอร์เหล่านั้นคือ

  • backend: อินสแตนซ์ของบริการแบ็กเอนด์ที่ให้ข้อมูล
  • query: คำค้นหาที่จะส่งไปยังบริการที่ระบุโดย backend

ออบเจ็กต์ LoadParams มีข้อมูลเกี่ยวกับการดำเนินการโหลดที่จะดำเนินการ ซึ่งรวมถึงคีย์ที่จะโหลดและจำนวนรายการที่จะโหลด

ออบเจ็กต์ LoadResult มีผลลัพธ์ของการดำเนินการโหลด LoadResult เป็นคลาสที่ปิดผนึก ซึ่งมี 3 รูปแบบ โดยขึ้นอยู่กับว่าการเรียก load สำเร็จหรือไม่

  • หากโหลดสำเร็จ ให้แสดงออบเจ็กต์ LoadResult.Page
  • หากโหลดไม่สำเร็จ ให้แสดงออบเจ็กต์ LoadResult.Error
  • หาก PagingSource ไม่ถูกต้องอีกต่อไปและควรแทนที่ด้วยอินสแตนซ์ใหม่ (เช่น เนื่องจากการเปลี่ยนแปลงข้อมูลพื้นฐาน) ให้ส่งออบเจ็กต์ LoadResult.Invalid

รูปภาพต่อไปนี้แสดงวิธีที่ฟังก์ชัน load ในตัวอย่างนี้ รับคีย์สำหรับการโหลดแต่ละครั้งและระบุคีย์สำหรับการโหลดครั้งถัดไป

ในการเรียกใช้การโหลดแต่ละครั้ง ExamplePagingSource จะรับคีย์ปัจจุบัน
    และแสดงผลคีย์ถัดไปที่จะโหลด
รูปที่ 1 แผนภาพแสดงวิธีที่ load ใช้และอัปเดตคีย์

การติดตั้งใช้งาน PagingSource ต้องติดตั้งใช้งานเมธอด getRefreshKey ที่ใช้ออบเจ็กต์ PagingState เป็นพารามิเตอร์ด้วย โดยจะแสดงผลคีย์เพื่อส่งไปยังเมธอด load เมื่อมีการรีเฟรชหรือทำให้ข้อมูลไม่ถูกต้องหลังจากโหลดครั้งแรก ไลบรารีการแบ่งหน้าจะเรียกใช้เมธอดนี้โดยอัตโนมัติเมื่อรีเฟรชข้อมูลในภายหลัง

จัดการข้อผิดพลาด

คำขอโหลดข้อมูลอาจไม่สำเร็จด้วยเหตุผลหลายประการ โดยเฉพาะเมื่อโหลดผ่านเครือข่าย รายงานข้อผิดพลาดที่พบระหว่างการโหลดโดยการแสดงออบเจ็กต์ LoadResult.Error จากเมธอด load

เช่น คุณสามารถตรวจหาและรายงานข้อผิดพลาดในการโหลดใน ExamplePagingSource จากตัวอย่างก่อนหน้าได้โดยเพิ่มข้อมูลต่อไปนี้ลงในเมธอด load

catch (e: IOException) {
  // IOException for network failures.
  return LoadResult.Error(e)
} catch (e: HttpException) {
  // HttpException for any non-2xx HTTP status codes.
  return LoadResult.Error(e)
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับการจัดการข้อผิดพลาดของ Retrofit ได้ที่ตัวอย่างใน PagingSourceเอกสารอ้างอิง API

PagingSource จะรวบรวมและส่งออบเจ็กต์ LoadResult.Error ไปยัง UI เพื่อให้คุณดำเนินการกับออบเจ็กต์เหล่านั้นได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับการแสดงสถานะการโหลดใน UI ได้ที่จัดการและนำเสนอสถานะการโหลด

ตั้งค่าสตรีมของ PagingData

จากนั้นคุณต้องมีสตรีมข้อมูลที่แบ่งหน้าจากการติดตั้งใช้งาน PagingSource ตั้งค่าสตรีมข้อมูลใน ViewModel คลาส Pager มีเมธอดที่แสดงสตรีมแบบรีแอ็กทีฟของออบเจ็กต์ PagingData จาก PagingSource ไลบรารีการแบ่งหน้าจะแสดงสตรีมข้อมูลเป็น Flow

เมื่อสร้างอินสแตนซ์ Pager เพื่อตั้งค่าสตรีมแบบรีแอกทีฟ คุณต้อง ระบุออบเจ็กต์การกำหนดค่า PagingConfig และฟังก์ชันที่บอก Pager วิธีรับอินสแตนซ์ของออบเจ็กต์การติดตั้งใช้งาน PagingSource ตามที่แสดงในตัวอย่างต่อไปนี้

ที่แสดงผลด้วย
class UserViewModel(
    private val backend: ExampleBackendService,
    private val query: String
) : ViewModel() {

    val userPagingFlow: Flow<PagingData<User>> = Pager(
        // Configure how data is loaded by passing additional properties to
        // PagingConfig, such as pageSize and enabling or disabling placeholders.
        config = PagingConfig(
            pageSize = 20,
            enablePlaceholders = true
        ),
        pagingSourceFactory = {
            ExamplePagingSource(backend, query)
        }
    )
    .flow
    .cachedIn(viewModelScope)
}

ตัวดำเนินการ cachedIn ทำให้สตรีมข้อมูลแชร์ได้และแคชข้อมูลที่โหลดด้วย CoroutineScope ที่ระบุ หากไม่มี cachedIn ระบบจะเรียกคืน PagingData ไม่ได้ ตัวอย่างนี้ใช้ viewModelScope ที่ได้จากอาร์ติแฟกต์ วงจร lifecycle-viewmodel-ktx

ออบเจ็กต์ Pager จะเรียกใช้เมธอด load จากออบเจ็กต์ PagingSource โดยส่งออบเจ็กต์ LoadParams ไปให้และรับออบเจ็กต์ LoadResult กลับมา

รวบรวมและแสดงข้อมูลใน UI

หากต้องการเชื่อมต่อสตรีมแบบแบ่งหน้ากับ UI ให้รับโฟลว์จาก ViewModel และ ส่งไปยังรายการที่ใช้ได้

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    val userFlow = viewModel.userPagingFlow
    UserList(flow = userFlow)
}

ใช้ collectAsLazyPagingItems เพื่อแปลงโฟลว์ PagingData เป็น LazyPagingItems จากนั้นใช้ items API ภายใน LazyColumn เพื่อจัดวาง แต่ละรายการ

โปรดระบุตัวระบุแบบคงที่ที่ไม่ซ้ำกันสำหรับสินค้าแต่ละรายการโดยใช้ itemKey ตัวอย่างต่อไปนี้ใช้ it.id (อ้างอิงพร็อพเพอร์ตี้ User.id) เนื่องจาก พร็อพเพอร์ตี้นี้จะคงที่สำหรับอินสแตนซ์ User เมื่อมีการอัปเดตข้อมูล

@Composable
fun UserList(flow: Flow<PagingData<User>>) {
    val lazyPagingItems = flow.collectAsLazyPagingItems()
    LazyColumn {
        items(
            lazyPagingItems.itemCount,
            key = lazyPagingItems.itemKey { it.id }
        ) { index ->
            val user = lazyPagingItems[index]
            if (user != null) {
                UserRow(user)
            } else {
                UserPlaceholder()
            }
        }
    }
}

ไลบรารีการแบ่งหน้าใช้ null เป็นตัวยึดตำแหน่งขณะที่โหลดหน้าเว็บ ดังนั้นหากเปิดใช้ตัวยึดตำแหน่ง คุณต้องจัดการค่า null ในบล็อกเนื้อหา

ตอนนี้รายการจะแสดงข้อมูลที่แบ่งหน้า และ Paging Library จะโหลดหน้าเพิ่มเติม เมื่อผู้ใช้เลื่อน

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับไลบรารี Paging ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้

เอกสารประกอบ

ดูเนื้อหา