시배's Android
TiTi Side Project | Infinite Recomposition Issue 본문
문제
TiTi에는 사용자가 자신의 공부 시간을 확인할 수 있는 TimeTable 기능이 있습니다. 어느 날 우연히 Layout Inspector를 사용하던 중, TimeTable에서 지속적으로 Recomposition 발생하고 있다는 사실을 발견했습니다.
문제의 원인
@Composable
fun TdsTimeTable(
modifier: Modifier = Modifier,
timeTableData: List<TdsTimeTableData>,
colors: List<Color>,
) {
var hour by remember {
mutableStateOf("0")
}
var fontSize by remember {
mutableStateOf(7.sp)
}
val textStyle = TdsTextStyle
.SEMI_BOLD_TEXT_STYLE
.getTextStyle(fontSize = fontSize)
.copy(color = TdsColor.TEXT.getColor())
val hourTextMeasurer = rememberTextMeasurer()
val hourTextLayoutResult = remember(hour) {
hourTextMeasurer.measure(hour, textStyle)
}
Canvas(
modifier = modifier
.drawWithContent {
drawContent()
drawGrid()
},
) {
val itemWidth = size.width / 7
val itemHeight = size.height / 24
fontSize = (itemHeight / 5).sp
repeat(24) { idx ->
hour = (idx + 6).let { if (it >= 24) it - 24 else it }.toString()
val startX = if (hour.length > 1) {
itemWidth / 2 - hourTextLayoutResult.size.width
} else {
itemWidth / 2 - hourTextLayoutResult.size.width / 2
}
val startY = itemHeight * (idx + 0.5f) - hourTextLayoutResult.size.height / 2
drawText(
textMeasurer = hourTextMeasurer,
text = hour,
style = textStyle,
topLeft = Offset(
x = startX,
y = startY,
),
)
}
timeTableData.forEachIndexed { index, timeTableData ->
val idx = (timeTableData.hour - 6).let { if (it < 0) it + 24 else it }
val startX = itemWidth + itemWidth * 6 * timeTableData.start / 3600f
val startY = itemHeight * idx
val barWidth = itemWidth * 6 * (timeTableData.end - timeTableData.start) / 3600
drawRoundRect(
color = colors[index % colors.size],
cornerRadius = CornerRadius(itemHeight / 5, itemHeight / 5),
topLeft = Offset(
x = startX,
y = startY,
),
size = Size(
width = barWidth,
height = itemHeight,
),
)
}
}
}
위 코드는 TdsTimeTable 컴포저블 함수를 통해 타임 테이블을 그리는 예시입니다. drawGrid 함수로 격자 모양을 그린 후, 첫 번째 칸에는 시간을 표시하고 나머지 칸에는 사용자의 공부 시간을 막대 그래프로 나타냅니다. 막대 그래프는 각 시간대에 맞춰 표시되며, timeTableData 리스트와 colors 리스트를 이용해 다양한 색상으로 표현됩니다.
var hour by remember { mutableStateOf("0") }
drawText(
textMeasurer = hourTextMeasurer,
text = hour,
style = textStyle,
topLeft = Offset(
x = startX,
y = startY,
),
)
recomposition이 지속적으로 발생하는 이유는 hour가 remember를 통해 상태가 관리되다 보니, Canvas를 통해 onDraw 될 때마다 hour가 변경되어 recomposition이 발생하는 문제였습니다.
해결
repeat(24) { idx ->
val hour = (idx + 6).let { if (it >= 24) it - 24 else it }.toString()
val hourTextLayoutResult = hourTextMeasurer.measure(hour, textStyle)
val startX = if (hour.length > 1) {
itemWidth / 2 - hourTextLayoutResult.size.width
} else {
itemWidth / 2 - hourTextLayoutResult.size.width / 2
}
val startY = itemHeight * (idx + 0.5f) - hourTextLayoutResult.size.height / 2
drawText(
textMeasurer = hourTextMeasurer,
text = hour,
style = textStyle,
topLeft = Offset(
x = startX,
y = startY,
),
)
}
recomposition이 지속적으로 발생하는 문제는 hour의 상태를 remember로 관리하면서 발생했습니다. 이를 해결하기 위해 hour를 일반 변수로 변경하여 recomposition이 발생하지 않도록 수정할 수 있었습니다.
수정 후, Layout Inspector에서도 recomposition이 발생하지 않는 것을 확인할 수 있었습니다.
'Android > TiTi Side Project' 카테고리의 다른 글
TiTi Side Project | Subcompose 1 frame Lag Issue (0) | 2024.04.01 |
---|---|
TiTi Side Project | Bitmap을 갤러리 저장 or Share 하기 (0) | 2024.03.21 |
TiTi Side Project | Composable 함수를 Bitmap으로 변환하기 (0) | 2024.03.16 |
TiTi Side Project | TimeTable 격자 이슈 해결 (0) | 2024.02.11 |
TiTi Side Project | TimeTable 컴포넌트 구현하기 (0) | 2024.02.07 |