จัดการและนำเสนอสถานะการโหลด

ไลบรารีการแบ่งหน้าจะติดตามสถานะของคำขอโหลดสำหรับข้อมูลที่แบ่งหน้าและแสดงสถานะผ่านคลาส LoadState

ระบบจะให้LoadStateสัญญาณแยกกันสำหรับแต่ละLoadType และประเภทแหล่งข้อมูล (ไม่ว่าจะเป็น PagingSource หรือ RemoteMediator) ออบเจ็กต์ CombinedLoadStates ที่ Listener ระบุจะให้ข้อมูลเกี่ยวกับสถานะการโหลด จากสัญญาณทั้งหมดนี้ คุณสามารถใช้ข้อมูลโดยละเอียดนี้เพื่อแสดงตัวบ่งชี้การโหลดที่เหมาะสมต่อผู้ใช้

สถานะการโหลด

ไลบรารีการแบ่งหน้าจะแสดงสถานะการโหลดเพื่อใช้ใน UI ผ่านออบเจ็กต์ LoadState LoadState จะมีรูปแบบใดรูปแบบหนึ่งใน 3 รูปแบบต่อไปนี้ ขึ้นอยู่กับ สถานะการโหลดปัจจุบัน

  • หากไม่มีการดำเนินการโหลดที่ใช้งานอยู่และไม่มีข้อผิดพลาด LoadState จะเป็นออบเจ็กต์ LoadState.NotLoading คลาสย่อยนี้ยังมีพร็อพเพอร์ตี้ endOfPaginationReached ซึ่งระบุว่าถึงจุดสิ้นสุดของการแบ่งหน้าแล้วหรือไม่
  • หากมีการดำเนินการโหลดที่ใช้งานอยู่ LoadState จะเป็นออบเจ็กต์ LoadState.Loading
  • หากมีข้อผิดพลาด LoadState จะเป็นออบเจ็กต์ LoadState.Error

เข้าถึงสถานะเหล่านี้ผ่านพร็อพเพอร์ตี้ loadState ของ Wrapper LazyPagingItems คุณใช้สถานะนี้ได้ 2 วิธี ได้แก่ การจัดการ ระดับการมองเห็นเนื้อหาหลัก (เช่น สปินเนอร์รีเฟรชแบบเต็มหน้าจอ) หรือการแทรก รายการที่กำลังโหลดลงในLazyColumnสตรีมโดยตรง (เช่น สปินเนอร์ส่วนท้าย)

เข้าถึงสถานะการโหลดด้วย Listener

หากต้องการตรวจสอบสถานะการโหลดใน UI ให้ใช้พร็อพเพอร์ตี้ loadState ที่จัดทำโดย Wrapper LazyPagingItems ซึ่งจะแสดงออบเจ็กต์ CombinedLoadStatesที่ช่วยให้คุณตอบสนองต่อลักษณะการทำงานของการโหลดสำหรับเหตุการณ์ รีเฟรช เพิ่ม หรือแทรก

ในตัวอย่างต่อไปนี้ UI จะแสดงไอคอนหมุนของการโหลดหรือข้อความแสดงข้อผิดพลาด ขึ้นอยู่กับสถานะปัจจุบันของการโหลด (เริ่มต้น) ที่รีเฟรช

@Composable
fun UserListScreen(viewModel: UserViewModel) {
  val pagingItems = viewModel.flow.collectAsLazyPagingItems()

  Box(modifier = Modifier.fillMaxSize()) {
    // Show the list content
    LazyColumn {
      items(pagingItems.itemCount) { index ->
        UserItem(pagingItems[index])
      }
    }

    // Handle the loading state
    when (val state = pagingItems.loadState.refresh) {
      is LoadState.Loading -> {
        CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
      }
      is LoadState.Error -> {
        ErrorButton(
          message = state.error.message ?: "Unknown error",
          onClick = { pagingItems.retry() },
          modifier = Modifier.align(Alignment.Center)
        )
      }
      else -> {} // No separate view needed for success/not loading
    }
  }
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับ LazyPagingItems ได้ที่ชุดข้อมูลขนาดใหญ่ (การแบ่งหน้า)

หากต้องการแสดงตัวบ่งชี้การโหลดที่จุดเริ่มต้นหรือจุดสิ้นสุดของรายการ (ทำหน้าที่เป็น ส่วนหัวหรือส่วนท้าย) ให้เพิ่มบล็อกรายการเฉพาะสำหรับสถานะเหล่านั้น ภายในLazyColumnขอบเขต

คุณตรวจสอบสถานะการเพิ่มข้อความนำหน้าสำหรับส่วนหัวและสถานะการเพิ่มข้อความต่อท้ายสำหรับส่วนท้ายได้โดยใช้ออบเจ็กต์ CombinedLoadStates

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

@Composable
fun UserList(viewModel: UserViewModel) {
  val pagingItems = viewModel.pager.flow.collectAsLazyPagingItems()

  LazyColumn {
    // 1. Header (Prepend state)
    // Useful if you support bidirectional paging or jumping to the middle
    item {
      val prependState = pagingItems.loadState.prepend
      if (prependState is LoadState.Loading) {
        LoadingItem()
      } else if (prependState is LoadState.Error) {
        ErrorItem(
          message = prependState.error.message ?: "Error",
          onClick = { pagingItems.retry() }
        )
      }
    }

    // 2. Main Data
    items(pagingItems.itemCount) { index ->
      UserItem(pagingItems[index])
    }

    // 3. Footer (Append state)
    // Shows when the user scrolls to the bottom and more data is loading
    item {
      val appendState = pagingItems.loadState.append
      if (appendState is LoadState.Loading) {
        LoadingItem()
      } else if (appendState is LoadState.Error) {
        ErrorItem(
          message = appendState.error.message ?: "Error",
          onClick = { pagingItems.retry() }
        )
      }
    }
  }
}

@Composable
fun LoadingItem() {
  Box(modifier = Modifier.fillMaxWidth().padding(16.dp), contentAlignment = Alignment.Center) {
    CircularProgressIndicator()
  }
}

@Composable
fun ErrorItem(message: String, onClick: () -> Unit) {
  Column(
    modifier = Modifier.fillMaxWidth().padding(16.dp),
    horizontalAlignment = Alignment.CenterHorizontally
  ) {
    Text(text = message, color = Color.Red)
    Button(onClick = onClick) { Text("Retry") }
  }
}

เข้าถึงข้อมูลสถานะการโหลดเพิ่มเติม

ดังที่แสดงในตัวอย่างก่อนหน้านี้ การโทรหา pagingItems.loadState.refresh นั้น สะดวก อย่างไรก็ตาม วิธีนี้จะบดบังความแตกต่างระหว่างการโหลดจากฐานข้อมูลในเครื่อง (PagingSource) กับเครือข่าย (RemoteMediator) ซึ่งอาจทำให้ UI แสดงวงกลมโหลดชั่วครู่แม้ว่าข้อมูลที่แคชจะพร้อมใช้งานทันทีก็ตาม

หากต้องการควบคุมอย่างแม่นยำ เช่น แสดงวงกลมหมุนขณะโหลดเฉพาะเมื่อฐานข้อมูลในเครื่อง ว่างเปล่าและเปิดใช้การซิงค์เครือข่าย ให้เข้าถึงพร็อพเพอร์ตี้ source และ mediator โดยตรงภายใน Composable

val loadState = pagingItems.loadState

val isSyncing = loadState.mediator?.refresh is LoadState.Loading

val isLocalEmpty = loadState.source.refresh is LoadState.NotLoading &&
                   pagingItems.itemSnapshotList.items.isEmpty()

if (isSyncing && isLocalEmpty) {
    FullScreenLoading()
} else {
    UserList(pagingItems)

    if (isSyncing) {
        TopOverlaySpinner()
    }
}

ตอบสนองต่อการเปลี่ยนแปลงสถานะการโหลด

คุณอาจต้องทริกเกอร์ผลข้างเคียงแบบครั้งเดียวตามการเปลี่ยนแปลงสถานะการโหลด เช่น การเลื่อนไปที่ด้านบนของรายการหรือแสดง Snackbar เมื่อการรีเฟรช เสร็จสมบูรณ์

ใช้ snapshotFlow ภายใน LaunchedEffect เพื่อสังเกตการเปลี่ยนแปลงสถานะเป็นสตรีม ซึ่งจะช่วยให้คุณใช้Flowตัวดำเนินการมาตรฐาน เช่น filter และ distinctUntilChanged เพื่อแยกเหตุการณ์ที่เฉพาะเจาะจงได้

val listState = rememberLazyListState()

LaunchedEffect(pagingItems) {
  // 1. Convert the state to a Flow
  snapshotFlow { pagingItems.loadState.refresh }
    // 2. Filter for the specific event (Refresh completed successfully)
    .distinctUntilChanged()
    .filter { it is LoadState.NotLoading }
    .collect {
      // 3. Trigger the side effect
      listState.animateScrollToItem(0)
    }
}

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

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

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

ดูเนื้อหา