문제 상황
ViewModel에서 서버에 요청을 보낸 후 받아온 응답을 State에 반영하였을 때,
Recomposition은 발생하는데 UI에는 데이터가 표시되지 않는 이슈가 있었습니다.
MainActivity.kt
먼저 MainScreen에선 아래와 같이 LazyColumn 내에 여러 레이아웃이 item으로 들어가는 구조입니다.
Mutli ViewType RecycleView 처럼 구성 되어있습니다.
@Composable
fun MainScreen(state: MainState) {
Log.d("MainScreen", "Top chart list: ${state.topChartList}")
Scaffold(
containerColor = WePLiTheme.color.black,
topBar = { /* appbar */ }
) { paddingValues ->
LazyColumn(
modifier = // modifier
verticalArrangement = Arrangement.spacedBy(24.dp),
contentPadding = PaddingValues(bottom = 40.dp)
) {
item { WePLiChartLayout(state.topChartList) }
item { WePLiBannerLayout() }
// etc items..
}
}
}
WePLiCahrtLayout
아이템으로 들어가는 WePLiChartLayout은 아래와 같이 구현되어있습니다.
특별한 것 없이 HorizontalPager로 구현 되어있습니다.
@Composable
fun WePLiChartLayout(musicList: List<ChartMusic>) {
if (musicList.isEmpty()) return
val pageCount = remember { musicList.size / 5 }
val pagerState = rememberPagerState(
pageCount = { pageCount }
)
Column {
TwoLineTitle(title = "위플리 TOP 100", subscription = "6월 23일 오전 7시 업데이트")
HorizontalPager(
modifier = // modifier
state = pagerState,
contentPadding = PaddingValues(start = 20.dp, end = 10.dp),
) { page ->
// item layout
}
}
}
State가 변경됨에 따라 WePLiChartLayout에서도 Recomposition이 발생했는데도 UI가 그려지지 않았기에
Skippable 대상이 되었다는 것까지는 생각 하였습니다.
시도 방법 1
처음에는 상위 LazyColumn에서의 문제인 줄 알았습니다.
그래서 item에 key 값을 state.topChartList.size으로 설정 해주었습니다.
LazyColum {
item(key = state.topChartList.size) { WePLiChartLayout(state.topChartList) }
}
문제점
결과적으로 UI 노출은 잘 되었으나 한 가지 문제점이 있었습니다.
만약 아래와 같이 다른 레이아웃에도 Key 지정이 필요하다면 어떻게 될까요?
LazyColumn {
item(key = state.topChartList.size) { WePLiChartLayout(state.topChartList) }
item(key = state.artistList.size) { ArtistLayout(state.artistList) }
}
기본적으로 LazyColumn/Row 내에서 item의 key는 유일해야합니다.
위와 같이 key를 할당하면 서버 응답을 수신하기 전 topChartList, artistList는 빈 리스트이므로 동일한 key 값을 가지게 됩니다.
hashcode를 이용해도 동일합니다.
결국 위의 방법으로는 해결할 수 없으며, 좋은 방법도 아닌 것 같습니다.
시도 방법 2 (해결)
상위 LazyColumn에선 해결할 수 있는 부분이 아닌 것 같아 다시 WePLiChartLayout을 조금 더 살펴 보았습니다.
그러다 아래 부분을 발견하였습니다.
val pageCount = remember { musicList.size / 5 }
val pagerState = rememberPagerState(
pageCount = { pageCount }
)
pageCount가 remember로 감싸져 있었습니다..
remember로 감싸진 부분은 Recomposition이 발생해도 다시 계산되지 않습니다.
pageCount가 최초 1회만 계산되고, 그 값이 pagerState로 전달되고 있었으니 topChartList가 변경되어도 pagerState가 바뀌지 않는 것이었습니다.
val pageCount = remember(key1 = musicList.size) { musicList.size / 5 }
val pagerState = rememberPagerState(
pageCount = { pageCount }
)
결국 remember에 key를 설정해주어 해결하였습니다.
pageCount를 계산하는 것이니 List의 사이즈가 변할 때에만 계산하도록 수정 하였습니다.
조금이라도 성능의 이점을 챙기고자 간단한 연산임에도 remember를 썼었던 것이었는데, pagerState를 고려하지 않고 무작정 쓴 게 잘못이었습니다.
remember 등을 사용할 땐 pagerState, scrollState 등을 잘 고려해서 쓸 필요가 있을 것 같습니다.
'안드로이드 > Compose' 카테고리의 다른 글
[Compose] - LazyRow 아이템 최대 높이로 고정하기 (8) | 2024.10.03 |
---|---|
[Compose] - Design System 구축 - 2. ColorScheme 만들기 (0) | 2024.08.04 |
[Compose] - Design System 구축 - 1. Typography 만들기 (0) | 2024.08.02 |
[ Compose ] - Compose Box (0) | 2024.05.19 |