RIEN😚
이상한 나라의 개발자
RIEN😚
전체 방문자
오늘
어제
  • 분류 전체보기 (125)
    • Algorithm (68)
      • 알고리즘 (0)
      • Baekjoon (8)
      • 프로그래머스 (55)
      • HackerRank (5)
    • Android (30)
      • Project (1)
      • Error (2)
      • Studio (1)
      • Android (26)
    • Kotlin (6)
    • CS (4)
      • 네트워크 (2)
      • 데이터베이스 (2)
    • Front End (5)
      • React (1)
      • VUE (3)
      • Project (0)
      • 기타 (1)
    • 기록 (11)
      • 회고록 (6)
      • TIL (5)

블로그 메뉴

  • Github🔥
  • 포트폴리오🌹

공지사항

인기 글

티스토리

250x250
반응형
hELLO · Designed By 정상우.
RIEN😚

이상한 나라의 개발자

Android/Android

[Android/Compose] Compose에서 Paging3 적용기

2023. 3. 3. 15:08
728x90
반응형

이번 토이 프로젝트에서 처음으로 Paging3와 Compose를 함께 사용하면서 부딪힌 이슈상황과

이를 해결하는 과정에 대해서 정리해보고자 Post를 적게 되었어요. 😭

 

혹시 잘못된 점이 있다면 꼭 댓글 부탁드립니다. 🙏

 

Paging3를 이전에도 사용해본적이 있기 때문에 Domain Layer 단까지는 쉽게 구현할 수 있었습니다.

🌹 Compose와 XML 구별 없이 동일한 방법이예요. :-)

 

ViewModel에서도 이전과 동일한 방법으로 아래와 같이 코드를 작성하였습니다.

val imagesInfo: Flow<PagingData<ImageUiModel>> =
    getImagesInfoUseCase()
        .map { it.map { image -> image.toUiModel() } }
        .cachedIn(viewModelScope)

 

다른 점이 있다면 PagingAdapter를 사용하는 XML과 달리

Compose에서는 아래와 같이 Flow를 State로 변경하여 Collect 해주게 됩니다.

val images = viewModel.imagesInfo.collectAsLazyPagingItems()

 

그 다음으로는 리스트의 상태를 관리해줄 LazyListState도 추가해줍니다.

val listState = rememberLazyListState()

이는 Recomposition이 일어나도 LazyList(LazyColumn, LazyRow 등)의 상태를 그대로 유지하기 위함이예요.👍🏻

 

이제 이 둘을 LazyList에서 사용하면 Paging3를 이용한 리스트 구현은 끝입니다! 

LazyVerticalGrid(
    modifier = modifier,
    cells = GridCells.Fixed(listType.column),
    state = listState
) {
    items(count = images.itemCount) { index ->
        images[index]?.let {
            // Item Compose Component
        }
    }
}

 

하지만 바로 여기서 생각치도 못한 이슈가 발생하였습니다! 📌

다른 화면으로 이동했다가 popBackStack으로 다시 현재 화면으로 돌아왔을 때 리스트의 스크롤 위치가 초기화되는 문제였습니다. (두둥! 😱)

 

다시 현재 화면으로 돌아왔을 때, 이전 스크롤 위치가 아닌 가장 상단 이미지가 보인다면 UX적으로 너~무나 좋지 않은 것 같아

이를 우선적으로 해결해보기로 하였습니다. 🤔

 

무엇이 문제였을까? 생각해보던 중 리스트의 Scroll 위치를 관리하는 LazyListState에 문제가 있는 것일까하는 의문이 들게 되었습니다.

그러나 rememberLazyListState() 코드를 열어보면

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex,
            initialFirstVisibleItemScrollOffset
        )
    }
}

 

rememberSaveable로 관리되고 있는 것을 확인할 수 있었어요....ㅎㅎ

원인을 찾아 디버깅과 구글링을 반복하던 중

 

popBackStack 시, PagingData의 itemcount 값이 0으로 한번 초기화 되었다가 다시 item을 가지고 오는 것을 알 수 있었습니다.

 

어.. 어째서😭

문제는 바로 collectAsLazyPagingItem 내부 코드에 있었습니다.

@Composable
public fun <T : Any> Flow<PagingData<T>>.collectAsLazyPagingItems(): LazyPagingItems<T> {
    val lazyPagingItems = remember(this) { LazyPagingItems(this) }

    LaunchedEffect(lazyPagingItems) {
        lazyPagingItems.collectPagingData()
    }
    LaunchedEffect(lazyPagingItems) {
        lazyPagingItems.collectLoadState()
    }

    return lazyPagingItems
}

 

이 LazyPagingItems가 rememberSaveable이 아니여서 popBackStack 시 새로 생성되게 됩니다. 😳

이 때 생성되는 LazyPagingItems 내부에 itemcount 프로퍼티를 보게 되면

var itemSnapshotList by mutableStateOf(
    ItemSnapshotList<T>(0, 0, emptyList())
)
    private set

/**
 * The number of items which can be accessed.
 */
val itemCount: Int get() = itemSnapshotList.size

 

snapShot의 초기값이 emptyList여서 itemCount의 첫 값은 0이고,

LauncedEffect 내에 lazyPgingItems.collectPagingData() 가 호출된 이후에야 PagingData의 itemcount 값을 set하기 때문에

위에서 봤던 이미지처럼

일단 한번 itemcount가 0이 되는 순간이 발생하게 되는 듯 합니다.

 

itemcount가 일단 한번 0으로 설정되면 어떤 일이 발생하게 될까요?

LazyVerticalGrid(
    modifier = modifier,
    cells = GridCells.Fixed(listType.column),
    state = listState
) {
    items(count = images.itemCount) { index ->
        images[index]?.let {
            // Item Compose Component
        }
    }
}

 

리스트를 그리는 Compose를 다시 한번 보게 된다면 images.itemCount에 맞게 리스트의 item들을 생성하기 때문에

리스트에 있는 모든 item들이 제거된 후 다시 그려지는 작업을 수행하게 됩니다.

 

이것이 바로 LazyListState가 정상적으로 동작하지 않았던 이유입니다. 🪴

 

이를 해결하기 위해서는 itemcount가 0이 되었을 때 기존 리스트에 있던 item들의 제거를 피하기 위해 아래와 같은 분기처리가 필요하게 됩니다.🔥

if (images.itemCount != 0) {
    LazyVerticalGrid(
        modifier = modifier,
        cells = GridCells.Fixed(listType.column),
        state = listState
    ) {
        items(count = images.itemCount) { index ->
            images[index]?.let {
            	// item compose component
            }
        }
    }
}

 

결론은 PagingData의 itemCount가 0이 되었을 때 별도의 처리를 해주자!

뭐야 rememberSaveable로 된 것도 만들어줘요. 😭

 

 


간단하면서도 제 프로젝트 진행 시간을 많이 뺏어먹었던 Compose에서의 Paging3 이용 과정이었습니다.ㅎㅎ

 

감사합니다!

반응형

'Android > Android' 카테고리의 다른 글

[Android/Compose] 언제 Recomposition이 발생하는 것일까?  (0) 2023.03.26
[Android] Retrofit의 ThreadPool : 왜 Retrofit은 Dispatcher 변경을 하지 않아도 되는것일까..  (0) 2023.03.10
[공식문서 열어보기] AlarmManger로 정확한 시간에 알림 띄워주자  (0) 2023.02.13
[Hilt] Component와 Scope 무엇이 다른지 디버깅으로 다 찍어보자!  (0) 2023.02.11
[Android:Codelab] Dependency Injection and Test Doubles - 5  (0) 2023.01.26
    'Android/Android' 카테고리의 다른 글
    • [Android/Compose] 언제 Recomposition이 발생하는 것일까?
    • [Android] Retrofit의 ThreadPool : 왜 Retrofit은 Dispatcher 변경을 하지 않아도 되는것일까..
    • [공식문서 열어보기] AlarmManger로 정확한 시간에 알림 띄워주자
    • [Hilt] Component와 Scope 무엇이 다른지 디버깅으로 다 찍어보자!
    RIEN😚
    RIEN😚
    안드로이드 / 코틀린 독학으로 취업하자!

    티스토리툴바