터닝에서 4차 프린트가 끝나고 리팩토링을 진행하기로 했다.
➡️터닝 레포지토리: https://github.com/teamterning/Terning-Android
어떤 리팩토링을 진행할지 회의를 했는데, 다같이 진행하기 좋은 리팩토링은 "리컴포지션 잡기"라고 생각했다.
따라서 각자 구현한 뷰들의 리컴포지션 여부를 파악하고 이를 다양한 방법으로 개선해보기로 했다.
그 해결방법을 간단하게 소개해보고자 한다!

위 사진은 지금까지 터닝의 리팩토링 과정을 정리한 표이다.
한번 쓰윽 구경하고 가도 좋을 것 같다:)
이벤트 파라미터 분리
리컴포지션이 발생하는 부분을 파악하기 위해 Layout Insepctor를 활용하였다.
Layout Insepctor는 상단 Tools를 클릭하면 바로 확인할 수 있다.

그럼 화면에 이벤트가 발생할 때마다 숫자가 올라가는 것을 볼 수 있는데
왼쪽 숫자는 "Number of times this composable has been recomposed" 즉, 리컴포지션일 발생한 횟수를 나타내고
오른쪽 숫자는 "Number of times recomposition for this component has been skipped" 즉, 리컴포지션이 스킵된 횟수를 나타낸다.
눈길이 간 화면은 마이페이지 수정 뷰였다.
위 영상에서 ProfileWithPlusButton을 유의해서 봐주길 바란다.
해당 컴포저블 함수는 상단에 있는 프로필 이미지를 그리는 함수다. 즉, 텍스트를 입력할 때는 리컴포지션이 될 필요가 없는 함수이다.
그러나 텍스트를 누를 때마다 의미없는 리컴포지션 횟수가 발생하고 있다.
✅기존 코드
ProfileWithPlusButton(
modifier = Modifier
.noRippleClickable {
onProfileEditClick(true)
}
.align(Alignment.CenterHorizontally),
profileImage = profileEditState.profile
)
클릭을 할 때마다 Modifier를 생성해서 넘기는 형태로 처리를 하고 있었다.
이렇게 하면 파라미터가 달라졌으니 리컴포지션을 발생시키는 것이었다.
✅수정한 코드
ProfileWithPlusButton(
onClick = {
onProfileEditClick(true)
},
modifier = Modifier
.align(Alignment.CenterHorizontally),
profileImage = profileEditState.profile
)
그래서 onClick 이라는 파라미터를 별도로 만들어 콜백만 최신 값으로 업데이트 해주도록 수정하였다.
✅결과
전과 다르게 리컴포지션이 전부 스킵되고 있는 걸 확인할 수 있다!
불변 모델로 매핑
이번에는 예전에 Compose Compiler Metrics를 설정해둔 것을 이용하여 파라미터들이 stable한지 파악하고자 했다.
➡️ 해당 아티클 : https://comyou.tistory.com/153
직접 확인해보니 다행히 대부분 다 stable 이었다.
그때 스플래쉬 화면에서 unstable한 부분을 찾았다!
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun SplashScreen(
unstable updateState: UpdateState // 여기!!
stable onUpdateButtonClick: Function0<Unit>
stable onUpdateSkipButtonClick: Function0<Unit>
)
바로 UpdateState가 unstable한 것이다.
UpdateState 코드는 아래와 같다.
sealed class UpdateState {
data object InitialState : UpdateState()
data object NoUpdateAvailable : UpdateState()
data class MajorUpdateAvailable(val title: String, val content: String) : UpdateState()
data class PatchUpdateAvailable(val title: String, val content: String) : UpdateState()
}
처음에는 @Immutable 어노테이션을 달아주려고 했다. 그러나 UpdateState는 domain 레이어에 존재하고 있었다. 즉, domain 레이어는 순수한 자바 코틀린 언어만 사용가능하기에 어노테이션이 사용 불가능했다.
그래서 어떻게 해결할지 고민하다가 예전에 Stability 관련 영상을 봤던 것이 기억났다.
➡️영상 링크: https://www.youtube.com/watch?v=bDyhdJk3uZM
정말 좋은 내용이 많으니 시간이 될 때 보면 좋을 것 같다:)
그 중 내가 떠올린 부분은 아래 코드이다.
바로, 불안정한 타입을 감싸는 Immutable Wrapper를 만들고, 이를 파라미터로 받도록 변경한 것이다.

그래서 나도 UI 레이어에 @Immutable을 단 전용 sealed class(SplashUiState)를 만들고, 매퍼(toUi())로 변환해 전달하였다.
@Immutable
sealed class SplashUiState {
data object InitialState : SplashUiState()
data object NoUpdateAvailable : SplashUiState()
data class MajorUpdateAvailable(val title: String, val content: String) : SplashUiState()
data class PatchUpdateAvailable(val title: String, val content: String) : SplashUiState()
}
fun UpdateState.toUi(): SplashUiState = when (this) {
UpdateState.InitialState -> SplashUiState.InitialState
UpdateState.NoUpdateAvailable -> SplashUiState.NoUpdateAvailable
is UpdateState.MajorUpdateAvailable -> SplashUiState.MajorUpdateAvailable(title, content)
is UpdateState.PatchUpdateAvailable -> SplashUiState.PatchUpdateAvailable(title, content)
}
그럼 기존에는 unstable했던 부분이 stable로 바뀐 것을 확인할 수 있다!!
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun SplashScreen(
stable splashUiState: SplashUiState // 해결!!
stable onUpdateButtonClick: Function0<Unit>
stable onUpdateSkipButtonClick: Function0<Unit>
)'Develop > Android' 카테고리의 다른 글
| [Android] FCM 푸시알림 커스텀 구현기 (딥링크 활용) (0) | 2025.04.28 |
|---|---|
| [Android] Baseline Profile로 성능 개선하기 (0) | 2025.02.18 |
| [Android] ProcessPhoenix 오픈소스 뜯어보기 (0) | 2025.02.02 |
| [Android] SnackBar 디자인시스템 구현기 (Material 뜯어보기) (1) | 2025.01.30 |
| [Android] BottomSheet 디자인시스템 구현기 (Material 뜯어보기) (2) | 2025.01.29 |