로드 상태 관리 및 표시

Paging 라이브러리는 페이징된 데이터의 로드 요청 상태를 추적하고 노출합니다. LoadState 클래스를 통해

LoadType 및 데이터 소스 유형( PagingSource 또는 RemoteMediator)마다 별도의 LoadState 신호가 제공됩니다. 리스너에서 제공하는 CombinedLoadStates 객체는 이러한 모든 신호의 로드 상태에 관한 정보를 제공합니다. 이 세부정보를 사용하여 사용자에게 적절한 로드 표시기를 표시할 수 있습니다.

로드 상태

Paging 라이브러리는 LoadState 객체를 통해 UI에서 사용할 로드 상태를 노출합니다. LoadState 객체는 현재 로드 상태에 따라 다음 세 가지 형식 중 하나를 취합니다.

  • 활성 로드 작업이 없고 오류가 없는 경우 LoadStateLoadState.NotLoading 객체입니다. 이 서브클래스에는 페이지로 나누기의 끝에 도달했는지를 나타내는 endOfPaginationReached 속성이 포함됩니다.
  • 활성 로드 작업이 있는 경우 LoadStateLoadState.Loading 객체입니다.
  • 오류가 있는 경우 LoadStateLoadState.Error 객체입니다.

LazyPagingItems 래퍼의 loadState 속성을 통해 이러한 상태에 액세스합니다. 이 상태는 기본 콘텐츠 공개 상태를 처리하거나 (예: 전체 화면 새로고침 스피너) 로드 항목을 LazyColumn 스트림에 직접 삽입하는 (예: 바닥글 스피너) 두 가지 방법으로 사용할 수 있습니다.

리스너로 로드 상태에 액세스하기

UI에서 로드 상태를 모니터링하려면 loadState 속성 에서 제공하는 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에 로드 스피너가 잠시 표시될 수 있습니다.

로컬 데이터베이스가 비어 있고 네트워크 동기화가 활성 상태일 때만 로드 스피너를 표시하는 것과 같은 정밀한 제어를 위해서는 구성 가능한 함수 내에서 sourcemediator 속성에 직접 액세스하세요.

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를 표시하는 등 로드 상태 변경에 따라 일회성 부수 효과를 트리거해야 할 수 있습니다.

LaunchedEffect 내에서 snapshotFlow를 사용하여 상태 변경을 스트림으로 관찰합니다. 이렇게 하면 Flow 연산자를 적용하여 특정 이벤트를 격리할 수 있습니다.filterdistinctUntilChanged

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)
    }
}

추가 리소스

Paging 라이브러리 및 로드 상태에 관한 자세한 내용은 다음 리소스를 참고하세요.

문서

콘텐츠 보기