
Android μ± κ°λ°μ νλ€ λ³΄λ©΄
μλ²λ‘λΆν° ν λ²μ λͺ¨λ λ°μ΄ν°λ₯Ό κ°μ Έμ€κΈ° μ΄λ €μ΄ κ²½μ°κ° λ§μ΅λλ€.
μ΄λ΄ λ μ¬μ©νλ λνμ μΈ κΈ°μ μ΄ λ°λ‘
π Paging μ
λλ€.
νΉν Paging3 + Jetpack Compose μ‘°ν©μ
λ°©λν λ°μ΄ν°λ₯Ό 리μ€νΈ ννλ‘ μμ°μ€λ½κ², λκΉ μμ΄ λ³΄μ¬μ€ μ μμ΄
μ€λ¬΄μμ λ리 νμ©λ©λλ€.
μ λ κ°λ°νλ©΄μ μ¬μ©μ κ²½ν(UX)μ 무μλ³΄λ€ μ€μνκ² μκ°ν©λλ€.
λ°μ΄ν°κ° λ‘λλλ λμ νλ©΄μ΄ ν
λΉμ΄ μμΌλ©΄,
μ¬μ©μλ λΆμνκ² μκ°ν©λλ€.
"μ±μ΄ λ©μΆ 건κ°?" νλ μλ¬Έμ κ°κ² λμ£ .
κ·Έλμ μ λ λ‘λ© μνμμ λ¨μ μ€νΌλ λμ
π Skeleton Loading(μ€μΌλ ν€ λ‘λ©)μ μ μ©ν©λλ€.
π‘ μ€μΌλ ν€ λ‘λ©μ μ€μ μ½ν μΈ μ λ μ΄μμμ νμ λΈλ‘ λ±μΌλ‘ 미리 κ·Έλ € 보μ¬μ£Όμ΄
μ¬μ©μκ° "κ³§ λ°μ΄ν°κ° λνλκ² κ΅¬λ" λΌλ μ¬λ¦¬μ μμ κ°μ λλ μ μκ² νλ λ°©μμ λλ€.
LazyPagingItemsλ₯Ό μ°λ©΄ λ°μ΄ν° λ‘λ© μνλ₯Ό μ μ μλ loadStateκ° μ 곡λ©λλ€.
refresh β μ΄κΈ° λ‘λ© / μλ‘κ³ μΉ¨ append β 리μ€νΈ νλ¨ μΆκ° λ‘λ© κ°κ° Loading, Error, NotLoading μνλ₯Ό κ°μ§λλ€.
μ¦, μ°λ¦¬κ° ν΄μΌ ν μΌμ κ°λ¨ν©λλ€:
Paging3λ₯Ό Composeμμ μ¬μ©ν λ κ°μ₯ νν ν¨ν΄μ
κ° νλ©΄μμ loadStateλ₯Ό μ§μ λΆκΈ° μ²λ¦¬νλ λ°©μμ
λλ€.
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(top = 8.dp),
state = scrollState,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(
key = bookListPagingItems.itemKey { it.isbn },
count = bookListPagingItems.itemCount
) { index ->
/**
* PagingItemμ λν Composable
*/
}
val loadState = bookListPagingItems.loadState
when {
loadState.refresh is LoadState.Loading -> {
item {
/**
* μλ‘κ³ μΉ¨ μν(μ΄κΈ° λ‘λ©) λ‘λ© UI (μ: Skeleton)
*/
}
}
loadState.refresh is LoadState.Error -> {
val error = loadState.refresh as LoadState.Error
item {
/**
* μλ‘κ³ μΉ¨ μν μλ¬ UI (errorλ₯Ό λ°μ μ¬μλ μ 곡)
*/
}
}
loadState.append is LoadState.Loading -> {
item {
/**
* μΆκ° λ‘λ© μν λ‘λ© UI (μ: νλ¨ Progress)
*/
}
}
loadState.append is LoadState.Error -> {
val error = loadState.append as LoadState.Error
item {
/**
* μΆκ° λ‘λ© μν μλ¬ UI (errorλ₯Ό λ°μ μ¬μλ μ 곡)
*/
}
}
}
}
when(loadState) λΈλ‘μ μμ±ν΄μΌ ν¨ pagingLoadStateHandlerμμμ λ³Έ κ²μ²λΌ LoadStateλ₯Ό μ§μ μ²λ¦¬νλ©΄
μ€λ³΅ μ½λ, μ μ§λ³΄μ λΉμ©, UX λΆμΌκ΄μ± λ± μ¬λ¬ λ¬Έμ κ° λ°μν©λλ€.
μ λ μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ LazyListScopeμ νμ₯ ν¨μλ₯Ό λ§λ€μ΄
LoadState μ²λ¦¬λ₯Ό 곡ν΅ννμ΅λλ€.
/**
* νμ΄μ§ μνμ λ°λ₯Έ UI μ²λ¦¬λ₯Ό μν LazyListScope νμ₯ ν¨μ.
*
* @param pagingItems LazyPagingItems μΈμ€ν΄μ€.
* @param loadStateRefreshLoading μλ‘κ³ μΉ¨ μν(μ΄κΈ° λ‘λ©)μμ λ‘λ© UIλ₯Ό νμνλ μ»΄ν¬μ λΈ.
* @param loadStateRefreshError μλ‘κ³ μΉ¨ μνμμ μλ¬ UIλ₯Ό νμνλ μ»΄ν¬μ λΈ. LoadState.Error κ°μ²΄λ₯Ό νλΌλ―Έν°λ‘ λ°μ.
* @param loadStateAppendLoading μΆκ° λ‘λ© μνμμ λ‘λ© UIλ₯Ό νμνλ μ»΄ν¬μ λΈ.
* @param loadStateAppendError μΆκ° λ‘λ© μνμμ μλ¬ UIλ₯Ό νμνλ μ»΄ν¬μ λΈ. LoadState.Error κ°μ²΄λ₯Ό νλΌλ―Έν°λ‘ λ°μ.
*/
fun LazyListScope.pagingLoadStateHandler(
pagingItems: LazyPagingItems<*>,
loadStateRefreshLoading: @Composable LazyItemScope.() -> Unit = {},
loadStateRefreshError: @Composable LazyItemScope.(LoadState.Error) -> Unit = {},
loadStateAppendLoading: @Composable LazyItemScope.() -> Unit = {},
loadStateAppendError: @Composable LazyItemScope.(LoadState.Error) -> Unit = {}
) {
val loadState = pagingItems.loadState
when {
loadState.refresh is LoadState.Loading -> {
item {
loadStateRefreshLoading()
}
}
loadState.refresh is LoadState.Error -> {
val error = loadState.refresh as LoadState.Error
item {
loadStateRefreshError(error)
}
}
loadState.append is LoadState.Loading -> {
item {
loadStateAppendLoading()
}
}
loadState.append is LoadState.Error -> {
val error = loadState.append as LoadState.Error
item {
loadStateAppendError(error)
}
}
}
}
μ΄μ νλ©΄μμλ μ΄λ κ² λ¨ ν μ€λ§ μΆκ°νλ©΄ λ©λλ€ π
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(top = 8.dp),
state = scrollState,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(
key = bookListPagingItems.itemKey { it.isbn },
count = bookListPagingItems.itemCount
) { index ->
bookListPagingItems[index]?.let { book ->
BookContent(bookModel = book)
}
}
// β
LoadState κ³΅ν΅ μ²λ¦¬
pagingLoadStateHandler(
pagingItems = bookListPagingItems,
loadStateRefreshLoading = { SkeletonBookLoading() }, // μ΄κΈ° λ‘λ© β Skeleton UI
loadStateRefreshError = { e -> LoadStateRefreshError(error = e) { onRetry() } },
loadStateAppendLoading = { LoadStateAppendLoading() }, // νλ¨ μΆκ° λ‘λ© β Progress
loadStateAppendError = { e -> LoadStateAppendError(error = e) { onRetry() } },
)
}
μ΄ λ°©μ(pagingLoadStateHandler μ§μ νΈμΆ)μ λΆλͺ
νΈλ¦¬ν΄μ‘μ§λ§,
μ€μ λ‘ μ¬λ¬ νλ©΄μ μ μ©νλ€ λ³΄λ μ‘°κΈ μμ¬μ΄ μ μ΄ μμμ΅λλ€.
loadStateRefreshLoading, loadStateRefreshError,loadStateAppendLoading, loadStateAppendErrorλ₯Ό λͺ¨λ λ겨μ€μΌ ν¨ μ¦, Loading UIλ λ‘λ©μμ νμ λ
ΈμΆλμ΄μΌνκ³ μ€μ νλ©΄μμλ λ°λ μ΄λ²€νΈλ μ¬μλ μ΄λ²€νΈ λΏμΈλ°
λΆνμνκ² μ½λκ° κΈΈμ΄μ§λ λλμ΄ μμμ΅λλ€.
handlePagingLoadStateκ·Έλμ μ λ νλ‘μ νΈμ λ§κ² ν λ¨κ³ λ λνν ν¨μλ₯Ό λ§λ€μμ΅λλ€.
μ΄ ν¨μ μμμ λ‘λ©/μλ¬ UIλ₯Ό κ³ μ ν΄λκ³ ,
νλ©΄μμλ onRetry νλλ§ λ겨주면 λ©λλ€.
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(top = 8.dp),
state = scrollState,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(
key = bookListPagingItems.itemKey { it.isbn },
count = bookListPagingItems.itemCount
) { index ->
/**
* PagingItemμ λν Composable
*/
}
// β
LoadState κ³΅ν΅ μ²λ¦¬ (onRetryλ§ λ겨주면 λ¨)
handlePagingLoadState(
bookListPagingItems = bookListPagingItems,
onRetry = onRetry
)
}
π¨ κ·Έλ¦¬κ³ λνΌ ν¨μλ μ΄λ κ² μ μνμ΅λλ€:
private fun LazyListScope.handlePagingLoadState(
bookListPagingItems: LazyPagingItems<BookModel>,
onRetry: () -> Unit,
) {
pagingLoadStateHandler(
pagingItems = bookListPagingItems,
loadStateRefreshLoading = {
LoadStateRefreshLoading()
},
loadStateRefreshError = { error ->
LoadStateRefreshError(
error = error,
onClickRetry = onRetry
)
},
loadStateAppendLoading = {
LoadStateAppendLoading()
},
loadStateAppendError = { error ->
LoadStateAppendError(
error = error,
onClickRetry = onRetry
)
},
)
}
| κ΅¬λΆ | LoadState μ§μ μ²λ¦¬ | pagingLoadStateHandler (곡ν΅ν) | handlePagingLoadState (λ¨μν) |
|---|---|---|---|
| μ½λ κΈΈμ΄ | νλ©΄λ§λ€ κΈΈκ³ μ€λ³΅ | ν μ€λ‘ μ€μ΄λ¦, νμ§λ§ μΈμ λ§μ | onRetry νλλ§ λκΈ°λ©΄ λ |
| μ μ§λ³΄μ | UI λ°λλ©΄ λͺ¨λ νλ©΄ μμ | μ νΈ ν¨μλ§ μμ νλ©΄ μ 체 λ°μ | λνΌ ν¨μλ§ μμ νλ©΄ μ 체 λ°μ |
| UX μΌκ΄μ± | νλ©΄λ³λ‘ λ€λ₯΄κ² ꡬνλ μ μμ | κ³΅ν΅ μ²λ¦¬λ‘ μ΄λ μ λ 보μ₯ | μ μ UI κ³ μ β μμ ν΅μΌ |
| κ°λ μ± | 리μ€νΈμ μνμ²λ¦¬ λ€μμ | μ½λ λΆλ¦¬κ° λμ΄ κ°μ | κ°μ₯ κ°κ²°νκ³ κΉλ |
LoadState μ²λ¦¬λ λ°λ³΅μ μ΄κ³ μ€μνκΈ° μ¬μ΄ μμ
μ
λλ€. pagingLoadStateHandlerλ₯Ό λ§λ€λ©΄ μ€λ³΅ μ κ±° + μΌκ΄μ±μ μ»μ μ μμ΅λλ€. handlePagingLoadState κ°μ νλ‘μ νΈ λ§μΆ€ λνΌλ‘ λ¨μννλ©΄,onRetryλ§ λκΈ°λ©΄ λλ―λ‘ μ½λκ° κ°μ₯ κΉλν΄μ§λλ€. π νΉν μ λ UXλ₯Ό μ€μμνκΈ° λλ¬Έμ Skeleton Loadingμ κΈ°λ³ΈμΌλ‘ μ μ©ν΄,
μ¬μ©μμκ² "μ±μ΄ λ©μΆ κ² μλλΌ λ°μ΄ν°λ₯Ό λΆλ¬μ€λ μ€μ΄κ΅¬λ" νλ μμ κ°μ μ£Όλλ‘ νμ΅λλ€.
π¬ μ¬λ¬λΆμ Paging3μ λ‘λ© μνλ₯Ό μ΄λ»κ² μ²λ¦¬νκ³ κ³μ κ°μ?
λκΈλ‘ 곡μ ν΄μ£Όμλ©΄ μ’κ² μ΅λλ€ π