하는 핵심 구성 요소 • 순수한 Kotlin으로 작성된 Kotlin 컴파일러 플러그인 • KAPT나 KSP와 같은 annotation processor와는 달리, Compose 컴파일러는 FIR (Frontend Intermediate Representation)을 통하여 개발자가 작성한 코드를 정적 분석 및 변형
❏ Recomposition은 메모리 내 표현을 항상 최신 상태로 유지하기 위해 입력값의 변경이나 관찰 중인 State에 변경이 발생할 때 일어남 ❏ Compose 컴파일러는 상태를 관찰하는 모든 Composable 함수를 찾아서 Compose 런타임에게 정보를 전달함 Understanding Stability Restartable
recomposition을 트리거하는 State라는 효율적인 메커니즘을 제공 var text by remember { mutableIntStateOf(0) } Text( modifier = Modifier.clickable { text++ }, text = "Clicked $text times" ) Understanding Stability Recomposition
recomposition을 트리거하는 State라는 효율적인 메커니즘을 제공 2. Composable 함수의 매개변수 변경 Compose 런타임은 equals 함수를 사용하여 stable한 매개변수에 대해 입력값의 변경 사항을 감지함. 만약 equals 함수가 false를 반환하면 런타임은 이를 입력 데이터의 변경으로 해석 @Composable fun UserProfile(name: String, image: String) { .. } Understanding Stability Recomposition
} compile @Composable fun Profile( stable user: User, unstable posts: List<Post>, ) Understanding Stability Stable vs. Unstable ❏ Compose 컴파일러는 Composable 함수에 사용된 매개변수에게 stable 혹은 unstable 타입을 부여 ❏ Composable 함수에 unstable한 매개변수가 하나 이상 포함되어 있으면 recomposition이 항상 발생 ❏ Composable 함수가 모두 stable한 매개변수로 이루어져 있다면 recomposition을 건너뛰고 불필요한 작업 생략
타입 (Primitive types) ❏ (Int) -> String 와 같은 람다식으로 표현되는 함수의 유형 (외부값을 캡처하는 경우는 값이 stable인 경우만) ❏ (data) class의 public 프로퍼티가 모두 불변이거나 stable한 경우 ❏ (data) class에 @Stable 및 @Immutable 어노테이션을 사용하여 명시적으로 stable 하다고 표기된 경우
stable하고 해당 값이 변경되지 않은 경우 (equals() returns true), Compose는 관련 UI 컴포넌트에 대한 recomposition 작업을 생략 - 매개변수가 unstable하거나 stable하지만 값이 변경 된 경우 (equals() returns false), Compose 런타임은 recomposition을 수행하고 UI 레이아웃을 invalidate하여 다시 랜더링 2. Equality 체크 새로운 stable한 유형의 매개변수가 Composable 함수로 전달되면, equals() 메서드를 사용하여 이전의 매개변수 값과 equality(동등성) 확인을 수행
class의 모든 public 프로퍼티와 필드가 초기화 된 이후에 절대 변경되지 않도록(불변) 보장 @Immutable 어노테이션 사용에 대한 두 가지 규칙: 1. 모든 public 프로퍼티에 대하여 val keyword를 사용하여 불변임을 확인 2. 커스텀 setter 사용을 피하고, 모든 public 프로퍼티에 가변성이 없는지 확인
String, public val nickname: String, public val profileImage: String, ) Stable Unstable public data class User( public val id: String, public val nickname: String, public val images: List<String>, )
String, public val nickname: String, public val profileImage: String, ) Stable Unstable public data class User( public val id: String, public val nickname: String, public val profileImages: List<String>, ) @Immutable public data class User( public val id: String, public val nickname: String, public val profileImages: List<String>, )
강력한 약속이지만, @Immutable 보다는 조금 느슨한 약속을 의미 ❏ 문맥적으로 "Stable(안정적)"이라는 용어는 함수가 동일한 입력값에 대해 일관되게 동일한 결과를 반환하여, 잠재적인 변경 가능성에도 불구하고 예측 가능한 동작을 보장한다는 것을 의미 ❏ @Stable 어노테이션은 public 프로퍼티가 불변인 클래스 혹은 인터페이스에 가장 적합하지만, 클래스 자체나 인터페이스의 구현체가 안정적이지 않을 수 있는 경우에 사용됨
public val id: String, public val nickname: String, public val profileImages: List<String>, ) @Immutable @Stable @Stable interface UiState<T : Result<T>> { val value: T? val exception: Throwable? val hasSuccess: Boolean get() = exception == null }
suspend CoroutineScope.() → Unit ) { .. } @NonRestartableComposable • Composable 함수에 사용할 경우 Restartable 속성을 부여하지 않고 recomposition을 생략 • Recomposition의 영향을 받지 않아야 하는 경우 사용하기에 적합 • e.g, Composable 함수 내 상태 관리 및 사이드 이펙트 처리 및 표준 함수만을 호출하는 사례
listOf( "-P", "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" + "${project.absolutePath}/compose_compiler_config.conf" ) } compose_compiler_config.conf // Consider LocalDateTime stable java.time.LocalDateTime // Consider kotlin collections stable kotlin.collections.* // Consider my datalayer and all submodules stable com.datalayer.** // Consider my generic type stable based off it's first type parameter only com.example.GenericClass<*,_> // Consider our data models stable since we always use immutable classes com.google.samples.apps.nowinandroid.core.model.data.*
:feature:user public data class User( public val id: String, public val nickname: String, ) @Composable fun Profile( unstable user: User, ) Non-Skippable
• Compose Runtime에서 제공하는 stability marker 어노테이션(@Stable, @Immutable) 제공 • Compose에 의존성을 두지 않는 순수한 Kotlin 모듈 등에서 stability 어노테이션을 사용해야하는 경우 유용 • KMP 지원 github.com/skydoves/compose-stable-marker @Immutable public data class User( public val id: String, public val nickname: String, ) @Composable fun Profile( stable user: User, ) Skippable dependencies { compileOnly("com.github.skydoves:compose-stable-marker:1.0.4") }