ViewSystem은 명령형 UI방식이라고 한다. 이 방식은 UI변경 시, View객체 메서드의 명령적 호출을 통해 진행을 한다. 가령, textView.setText("...")
와 같은 식이다. ViewSystem으로 UI를 구성하기 위해선 반드시 위와 같은 메서드의 명령적 호출을 포함해야만 하며, 이는 단 하나의 메서드를 누락할 경우, UI가 이상해질 수도 있다는 점이 단점이다.
참조 : 안드로이드 공식 홈페이지
반면, Compose는 선언형 UI방식이라고 한다. 이 방식은 UI변경 시, 기존에 선언 된 '상태'값에 기반하여 UI를 자동 갱신해주는 점이 차이점이다. 선언형 UI방식은 단순 상태값 자체를 아규먼트로 해서 UI요소에 주입해준다. 가령 Text를 주입한다 했을 시 아래와 같이 진행된다.
var comment by remember { mutableStateOf("") }
Text(text=comment)
Button(
onClick = { comment = "helloWorld" }
)
즉, 버튼을 통해 상태값을 변경하면 그 값이 바로 Text
에 주입되는 것이다. Text는 내부적으로 setText
와 같은 메서드를 호출하지 않는다. 그리고 이는 개발자가 오로지 '상태'라는 녀석만 관리하면 되기에 유지보수 편리성을 제공한다.
하지만, 선언형 UI의 경우, 상태값이 변경되었을 때, 하나의 컴포저블 함수를 호출하게 되는데, 이 호출은 Root단의 UI도 호출할 수 있다. 이 과정에서 자식 컴포저블 함수들 또한 호출할 수 있는데 이 과정으로부터 불필요한 리컴포지션이 일어날 수 있다는 것이다.
즉, Compose를 좀 더 잘 활용하기 위해선, 스마트 리컴포지션 조건을 이해하는 것이 필요하며, 그 첫 걸음은 바로, 컴포즈 컴파일러가 지정하는 컴포저블 함수의 파라미터 타입을 아는 것부터 시작된다.
우린 컴포즈 함수를 정의함과 동시에 파라미터를 추가로 정의한다. 하지만 이때 주의를 기울여야 하는게, 부분별하게 파라미터를 선언한다면 해당 컴포즈 함수는 스마트 리컴포지션의 대상에서 제외가 될 수 있다는 점이다.
컴포즈 컴파일러 보고서에 따르면 스마트 리컴포지션이 가능한 컴포즈 함수를 'skippable'하다고 표현한다.
만약 스마트 리컴포지션이 불가능하다면, 'skippable' 키워드는 생략된다.
컴포즈 함수가 skippable(=스마트 리컴포지션이 가능하도록)해지려면 어떻게 할까? 이에 대한 핵심 키워드로 stable, unstable이 있다.
컴포즈 컴파일러가 컴포즈 함수 아규먼트 변경을 확신할 수 있다는 뜻이다. 대표적인 타입으로, 기본적인 Primitive타입과 이를 활용한 함수 타입이 있다. (ex. String, Boolean, (String) -> Boolean)
그리고 컴포즈 함수 파라미터 타입이 모두 위의 타입과 같다면, 이에 stable 키워드가 붙을 것이고, 해당 컴포즈 함수에는 'skippable' 키워드가 붙게된다. 그리고 이는 스마트 리컴포지션이 가능하단 뜻이다. 아래는 Now In Android의 컴포즈 컴파일러 보고서의 스크린 샷이다.
컴포즈 컴파일러가 컴포즈 함수 아규먼트 변경을 확신할 수 없다는 뜻이다. 대표적인 타입으로, 인터페이스 또는 클래스 타입이 있다. (ex. List, MutableList, SomeClass...)
그리고 컴포즈 함수 파라미터 타입이 위와 같다면, 이에 unstable키워드가 붙을 것이고, 해당 컴포즈 함수에는 'skippable'키워드가 붙지 않게 된다. 그리고 이는 스마트 리컴포지션에서 제외가 된다는 뜻이며, 리컴포지션의 대상에 무조건적으로 포함된다는 뜻이다. 아래는 Now In Android의 컴포즈 컴파일러 보고서의 스크린샷이다.
정리해보자. 기본 Primitive타입 파라미터가 붙은 컴포즈 컴파일러는 Stable한 타입이 될것이다. 그리고 모든 파라미터가 Stable하다면 해당 컴포즈 함수는 스마트 리컴포지션 대상에 포함되며 skippable키워드가 붙게 된다.
그렇다면 이런 생각이 들 수 있다.
unstable한 타입을 컴포즈 함수 파라미터로 사용하고 싶은데 그럴 수 없을까...?
이를 가능하게 해주는 키워드로 @Stable, @Immutable어너테이션의 사용과 ImmutableCollection API를 사용하는 것이 있다.
개발자가 임의 정의한 참조 타입을 컴포즈 함수 파라미터로 사용하고자할 때 유용하게 사용될 수 있다. 대표적인 예로, Now In Android에서 NiaAppState
모듈을 들 수 있다.
그리고 NiaAppState
모듈의 public한 property들이 변경되었을 때, NiaAppState
에 대한 상태 변수를 스마트 리컴포지션시키겠다는 뜻이다.
내부 객체 상태가 변경되지 않고, 새로운 객체가 주입될 경우에만 스마트 리컴포지션 대상에 포함시키겠다는 어너테이션이다.
대표적인 예로, staticCompositionLocalOf
API와 함께 앱 전체의 Background나 Theme를 설정할때 사용되곤 한다. 아래는 Now In Android의 사용 사례이다.
List타입의 인터페이스를 사용할 경우, 해당 파라미터는 unstable타입이 된다. 그 이유는 List는 인터페이스로써 하위 구현 객체의 변경 가능성을 내포할 수 있다. 이는 하위 타입 객체를 정확히 지정할 수 없을 뿐더러, 내부 값 상태 변경 또한 추적할 수 없음을 의미한다.
따라서 이를 가능하게 하려면 Jetbrain에서 배포한 ImmutableCollection
API를 사용하면 된다. 하지만 지금은 24년 5월 4일 기준으로 1.0.0 정식 Release가 되지 않은 이유로 API변경 가능성을 내비치고 있긴 하다. 그렇지만 해당 API가 컴포즈에서 나름 유용하게 쓰이지 않을까 생각한다. (현재 Now In Android에선 해당 API를 안쓰고 있다.)