Upgrade to Pro — share decks privately, control downloads, hide ads and more …

2025 컴포즈 마법사

Avatar for Ji Sungbin Ji Sungbin
November 15, 2025

2025 컴포즈 마법사

Avatar for Ji Sungbin

Ji Sungbin

November 15, 2025
Tweet

More Decks by Ji Sungbin

Other Decks in Programming

Transcript

  1. 안내 Kotlin v2.2.21 기준 Compose 기능 플래그 모두 활성된 기준

    StrongSkipping IntrinsicRemember OptimizeNonSkippingGroups 2025 마법콘
  2. gray box blue box green box @Composable @Composable @Composable fun

    fun fun () {
 (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox Box Box size background size background size background 300 250 100 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box
  3. @Composable fun GrayBox() {
 Box(
 Modifier.size(300.dp).background(Color.Gray),
 contentAlignment = Alignment.Center
 )

    {
 BlueBox()
 }
 }

 @Composable fun GreenBox() {
 Box(Modifier.size(100.dp).background(Color.Green))
 } @Composable fun () { (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 BlueBox // 크기 변경됨: 250.dp → 200.dp
 Box size background 200 dp Blue Center
 contentAlignment = GreenBox gray box blue box green box
  4. gray box blue box green box @Composable fun GrayBox() {


    Box(
 Modifier.size(300.dp).background(Color.Gray),
 contentAlignment = Alignment.Center
 ) {
 BlueBox()
 }
 }

 @Composable fun GreenBox() {
 Box(Modifier.size(100.dp).background(Color.Green))
 } @Composable fun () { (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 BlueBox // 이 컴포저블만 단독 리컴포지션되는 게 이상적인 상황
 Box size background 200 dp Blue Center
 contentAlignment = GreenBox
  5. @Composable @Composable @Composable fun fun fun () {
 (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox Box Box size background size background size background 300 200 100 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box // 리컴포지션이 불가능하다고 가정
 // 리컴포지션이 불가능하다고 가정
 gray box blue box green box
  6. @Composable @Composable @Composable fun fun fun () {
 (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox Box Box size background size background size background 300 200 50 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box // 여기에 변경이 일어나면.. (100.dp → 50.dp)
 gray box blue box green box
  7. @Composable @Composable @Composable fun fun fun () { (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox // 오직 이 박스만 리컴포지션 가능
 Box Box size background size background size background 300 200 50 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box gray box blue box green box
  8. @Composable @Composable @Composable fun fun fun () { (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox // GreenBox를 다시 그리려고 이 박스가 리컴포지션됨
 Box Box size background size background size background 300 200 50 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box gray box blue box green box
  9. @Composable @Composable @Composable fun fun fun () { (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox // GreenBox를 다시 그리려고 이 박스가 리컴포지션됨
 Box Box size background size background size background 300 200 50 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box // GreenBox를 다시 그리고자 이 박스도 다시 실행됨
 gray box blue box green box
  10. @Composable @Composable @Composable fun fun fun () { (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox // GreenBox를 다시 그리려고 이 박스가 리컴포지션됨
 // GreenBox를 다시 그리고자 이 박스도 다시 실행됨
 Box Box size background size background size background 300 200 50 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box // GrayBox, BlueBox가 모두 재실행되어야 변경 사항이 반영됨
 gray box blue box green box
  11. @Composable @Composable @Composable fun fun fun () { (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox // 여기에 변경이 일어나면.. (300.dp → 250.dp)
 Box Box size background size background size background 250 200 50 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box gray box blue box green box
  12. @Composable @Composable @Composable fun fun fun () { (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () {
 (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox // 여기에 변경이 일어나면.. (300.dp → 250.dp)
 Box Box size background size background size background 250 200 50 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box // 리컴포지션이 불가능한 이 박스도 재실행되고,
 gray box blue box green box
  13. @Composable @Composable @Composable fun fun fun () { (
 Modifier.

    ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (
 Modifier. ( . ). (Color. ),
 Alignment. ) {
 ()
 }
 }

 () { (Modifier. ( . ). (Color. ))
 } GrayBox BlueBox GreenBox // 여기에 변경이 일어나면.. (300.dp → 250.dp)
 // 리컴포지션이 불가능한 이 박스도 재실행되고,
 Box Box size background size background size background 250 200 50 dp Gray Center
 dp Blue Center
 dp Green contentAlignment = contentAlignment = BlueBox GreenBox Box // 리컴포지션이 불가능한 이 박스까지 모두 재실행됨
 gray box blue box green box
  14. @Composable @Composable inline fun (content: () -> Unit) {
 content()


    }
 InlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider InlineLambdaComposable Text "ROOT @ " "count: " "InlineLambdaContent @ " ${ } $ ${ } text = onClick =
  15. @Composable @Composable inline fun (content: () -> Unit) {
 content()


    }
 InlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider InlineLambdaComposable Text "ROOT @ " "count: " "InlineLambdaContent @ " ${ } $ ${ } text = onClick =
  16. @Composable @Composable inline fun (content: () -> Unit) {
 content()


    }
 InlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider InlineLambdaComposable Text "ROOT @ " "count: " "InlineLambdaContent @ " ${ } $ ${ } text = onClick =
  17. @Composable @Composable inline fun (content: () -> Unit) {
 content()


    }
 InlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider InlineLambdaComposable Text "ROOT @ " "count: " "InlineLambdaContent @ " ${ } $ ${ } text = onClick =
  18. @Composable @Composable inline fun (content: () -> Unit) {
 content()


    }
 InlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider InlineLambdaComposable Text "ROOT @ " "count: " "InlineLambdaContent @ " ${ } $ ${ } text = onClick =
  19. @Composable @Composable inline fun noinline ( content: () -> Unit)

    {
 content()
 }
 NoInlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider NoInlineLambdaComposable Text "ROOT @ " "count: " "NoInlineLambdaContent @ " ${ } $ ${ } text = onClick =
  20. @Composable @Composable inline fun noinline ( content: () -> Unit)

    {
 content()
 }
 NoInlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider NoInlineLambdaComposable Text "ROOT @ " "count: " "NoInlineLambdaContent @ " ${ } $ ${ } text = onClick =
  21. @Composable @Composable inline fun noinline ( content: () -> Unit)

    {
 content()
 }
 NoInlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider NoInlineLambdaComposable Text "ROOT @ " "count: " "NoInlineLambdaContent @ " ${ } $ ${ } text = onClick =
  22. @Composable @Composable fun (content: () -> Unit) {
 content()
 }


    NonInlineLambdaComposable Column {
 ( currentTimeMillis() )
 ( count , { count++ } )
 ()

 {
 ( currentTimeMillis() )
 }
 }
 Text Button HorizontalDivider NonInlineLambdaComposable Text "ROOT @ " "count: " "NonInlineLambdaContent @ " ${ } $ ${ } text = onClick =
  23. 컴포저블 람다 [스스로 리컴포지션이 불가능한 상황] 인라인되는 컴포저블 람다 [스스로

    리컴포지션이 가능한 상황] 인라인되지 않는 컴포저블 람다 2025 마법콘
  24. @Composable inline fun InlineComposable() {
 ( currentTimeMillis() )
 ( count

    , { count++ } )
 } Text Button "InlineContent @ " "count: " ${ } $ text = onClick = Column {
 ( currentTimeMillis() )
 ()

 ()
 } Text HorizontalDivider InlineComposable "ROOT @ " ${ }
  25. @Composable fun NonInlineComposable() {
 ( currentTimeMillis() )
 ( count ,

    { count++ } )
 }
 Text Button "NonInlineContent @ " "count: " ${ } $ text = onClick = Column {
 ( currentTimeMillis() )
 ()

 ()
 } Text HorizontalDivider InlineComposable "ROOT @ " ${ }
  26. Column {
 ( currentTimeMillis() )
 ()

 () { ( currentTimeMillis()

    )
 ( count , { count++ } ) }

 ()
 } Text HorizontalDivider Text Button Local "ROOT @ " "LocalContent @ " "count: " ${ } ${ } $ @Composable fun Local text = onClick =
  27. Column {
 ( currentTimeMillis() )
 ()

 () { ( currentTimeMillis()

    )
 ( count , { count++ } ) }

 ()
 } Text HorizontalDivider Text Button Local "ROOT @ " "LocalContent @ " "count: " ${ } ${ } $ @Composable fun Local text = onClick =
  28. Column {
 ( currentTimeMillis() )
 ()

 () { ( currentTimeMillis()

    )
 ( count , { count++ } ) }

 ()
 } Text HorizontalDivider Text Button Local "ROOT @ " "LocalContent @ " "count: " ${ } ${ } $ @Composable fun Local text = onClick =
  29. fun interface fun FunctionalComposable {
 ()
 } @Composable Content Column

    {
 ( currentTimeMillis() )
 () local = FunctionalComposable { ( currentTimeMillis() )
 ( count , { count++ } )
 }
 local. ()
 } Text HorizontalDivider Text Button Content "ROOT @ " "LocalFunctionalContent @ " "count: " ${ } ${ } $ val // remember 코드 생략
 text = onClick =
  30. fun interface fun FunctionalComposable {
 ()
 } @Composable Content Column

    {
 ( currentTimeMillis() )
 () local = FunctionalComposable { ( currentTimeMillis() )
 ( count , { count++ } )
 }
 local. ()
 } Text HorizontalDivider Text Button Content "ROOT @ " "LocalFunctionalContent @ " "count: " ${ } ${ } $ val // remember 코드 생략
 text = onClick =
  31. fun interface fun FunctionalComposable {
 ()
 } @Composable Content Column

    {
 ( currentTimeMillis() )
 () local = FunctionalComposable { ( currentTimeMillis() )
 ( count , { count++ } )
 }
 local. ()
 } Text HorizontalDivider Text Button Content "ROOT @ " "LocalFunctionalContent @ " "count: " ${ } ${ } $ val // remember 코드 생략
 text = onClick =
  32. 로컬 컴포저블 [스스로 리컴포지션이 불가능한 상황] 로컬에 정의된 컴포저블 [스스로

    리컴포지션이 가능한 상황] 로컬에 정의됐지만 functional interface인 
 컴포저블 2025 마법콘
  33. @Composable (): Int {
 ( currentTimeMillis() )
 ( count ,

    { count++ } )

 } fun return ReturnValueComposable Text Button "ReturnValueContent @ " "count: " ${ } $ text = onClick = 0
 Column {
 ( currentTimeMillis() )
 ()

 ()
 } Text HorizontalDivider ReturnValueComposable "ROOT @ " ${ }
  34. @Composable (): Int {
 ( currentTimeMillis() )
 ( count ,

    { count++ } )

 } fun return ReturnValueComposable Text Button "ReturnValueContent @ " "count: " ${ } $ text = onClick = 0
 Column {
 ( currentTimeMillis() )
 ()

 ()
 } Text HorizontalDivider ReturnValueComposable "ROOT @ " ${ }
  35. @Composable (): Int {
 ( currentTimeMillis() )
 ( count ,

    { count++ } )

 } fun return ReturnValueComposable Text Button "ReturnValueContent @ " "count: " ${ } $ text = onClick = 0
 Column {
 ( currentTimeMillis() )
 ()

 ()
 } Text HorizontalDivider ReturnValueComposable "ROOT @ " ${ }
  36. 반환하는 컴포저블 [스스로 리컴포지션이 불가능한 상황] 값을 반환하는 컴포저블 [스스로

    리컴포지션이 가능한 상황] 값을 반환하지 않는 컴포저블 (Unit) 2025 마법콘
  37. interface fun final class final override fun InterfaceComposable {
 ()


    } IFinalComposable : InterfaceComposable {
 () { ( currentTimeMillis() )
 ( count , { count++ } )
 }
 } @Composable @Composable Content Content // final 안 붙여도 기본으로 final 
 Text Button "IFinalContent @ " "count: " ${ } $ text = onClick = Column {
 ( currentTimeMillis() )
 ()

 { IFinalComposable() }. ()
 } Text HorizontalDivider remember Content "ROOT @ " ${ }
  38. interface fun final class final override fun InterfaceComposable {
 ()


    } IFinalComposable : InterfaceComposable {
 () { ( currentTimeMillis() )
 ( count , { count++ } )
 }
 } @Composable @Composable Content Content // final 안 붙여도 기본으로 final 
 Text Button "IFinalContent @ " "count: " ${ } $ text = onClick = Column {
 ( currentTimeMillis() )
 ()

 { IFinalComposable() }. ()
 } Text HorizontalDivider remember Content "ROOT @ " ${ }
  39. interface fun final class final override fun InterfaceComposable {
 ()


    } IFinalComposable : InterfaceComposable {
 () { ( currentTimeMillis() )
 ( count , { count++ } )
 }
 } @Composable @Composable Content Content // final 안 붙여도 기본으로 final 
 Text Button "IFinalContent @ " "count: " ${ } $ text = onClick = Column {
 ( currentTimeMillis() )
 ()

 { IFinalComposable() }. ()
 } Text HorizontalDivider remember Content "ROOT @ " ${ }
  40. interface fun final class final override fun InterfaceComposable {
 ()


    } IFinalComposable : InterfaceComposable {
 () { ( currentTimeMillis() )
 ( count , { count++ } )
 }
 } @Composable @Composable Content Content // final 안 붙여도 기본으로 final 
 Text Button "IFinalContent @ " "count: " ${ } $ text = onClick = Column {
 ( currentTimeMillis() )
 ()

 { IFinalComposable() }. ()
 } Text HorizontalDivider remember Content "ROOT @ " ${ }
  41. interface fun open class open override fun InterfaceComposable {
 ()


    } IOpenComposable : InterfaceComposable {
 () {
 ( currentTimeMillis() )
 ( count , { count++ } )
 }
 }
 @Composable @Composable Content Content Text Button "IOpenContent @ " "count: " ${ } $ text = onClick = Column {
 ( currentTimeMillis() )
 ()

 { IOpenComposable() }. ()
 } Text HorizontalDivider remember Content "ROOT @ " ${ }
  42. interface fun open class open override fun InterfaceComposable {
 ()


    } IOpenComposable : InterfaceComposable {
 () {
 ( currentTimeMillis() )
 ( count , { count++ } )
 }
 }
 @Composable @Composable Content Content Text Button "IOpenContent @ " "count: " ${ } $ text = onClick = Column {
 ( currentTimeMillis() )
 ()

 { IOpenComposable() }. ()
 } Text HorizontalDivider remember Content "ROOT @ " ${ }
  43. open composable [스스로 리컴포지션이 불가능한 상황] open 상태인 컴포저블
 [스스로

    리컴포지션이 가능한 상황] final 상태인 컴포저블 (interface, abstract class, open class 모두 해당) (암시적인 final 포함) 2025 마법콘
  44. 총 정리 [스스로 리컴포지션이 불가능한 상황] 인라인되는 컴포저블 인라인되는 컴포저블

    람다 로컬에 정의된 컴포저블 값을 반환하는 컴포저블 open 상태인 컴포저블 (functional interface 제외) 2025 마법콘
  45. 총 정리 [스스로 리컴포지션이 가능한 상황] 인라인되지 않는 컴포저블 인라인되는

    않는 컴포저블 람다 로컬에 정의되지 않은 컴포저블 반환값이 없는 컴포저블 final 상태인 컴포저블 (Unit) 2025 마법콘
  46. /** * 클래스나 인터페이스에 @Stable이 있는 경우, 다음 조건들이 반드시

    * 충족해야 합니다. * * 1. 동일한 두 인스턴스끼리 equals() 결과는 항상 true 입니다. * 2. public 프로퍼티의 값이 변경되면 컴포저블 함수가 알 수 있습니다. * 3. 모든 public 프로퍼티의 타입이 @Stable 입니다. * * 매개변수의 타입이 @Stable이라면, 컴포즈는 해당 매개변수의 인자를 * 비교하여 이전 호출과 동일하면 해당 컴포저블의 리컴포지션을 건너뜁니다. * * 함수에 @Stable이 있을 때는, 동일한 매개변수가 전달되면 항상 동일한 * 결과를 반환함을 의미합니다. */ annotation class Stable
  47. /** * * 1. 동일한 두 인스턴스끼리 equals() 결과는 항상

    true 입니다. * 3. 모든 public 프로퍼티의 타입이 @Stable 입니다. * * * 함수에 @Stable이 있을 때는, 동일한 매개변수가 전달되면 항상 동일한 * 결과를 반환함을 의미합니다. */ * 클래스나 인터페이스에 @Stable이 있는 경우, 다음 조건들이 반드시 * 충족해야 합니다. * 2. public 프로퍼티의 값이 변경되면 컴포저블 함수가 알 수 있습니다. * 매개변수의 타입이 @Stable이라면, 컴포즈는 해당 매개변수의 인자를 * 비교하여 이전 호출과 동일하면 해당 컴포저블의 리컴포지션을 건너뜁니다. annotation class Stable
  48. /** * * 1. 동일한 두 인스턴스끼리 equals() 결과는 항상

    true 입니다. * 3. 모든 public 프로퍼티의 타입이 @Stable 입니다. * * * 함수에 @Stable이 있을 때는, 동일한 매개변수가 전달되면 항상 동일한 * 결과를 반환함을 의미합니다. */ * 클래스나 인터페이스에 @Stable이 있는 경우, 다음 조건들이 반드시 * 충족해야 합니다. * 2. public 프로퍼티의 값이 변경되면 컴포저블 함수가 알 수 있습니다. * 매개변수의 타입이 @Stable이라면, 컴포즈는 해당 매개변수의 인자를 * 비교하여 이전 호출과 동일하면 해당 컴포저블의 리컴포지션을 건너뜁니다. annotation class Stable
  49. /** * * 1. 동일한 두 인스턴스끼리 equals() 결과는 항상

    true 입니다. * 3. 모든 public 프로퍼티의 타입이 @Stable 입니다. * * * 함수에 @Stable이 있을 때는, 동일한 매개변수가 전달되면 항상 동일한 * 결과를 반환함을 의미합니다. */ * 클래스나 인터페이스에 @Stable이 있는 경우, 다음 조건들이 반드시 * 충족해야 합니다. * 2. public 프로퍼티의 값이 변경되면 컴포저블 함수가 알 수 있습니다. * 매개변수의 타입이 @Stable이라면, 컴포즈는 해당 매개변수의 인자를 * 비교하여 이전 호출과 동일하면 해당 컴포저블의 리컴포지션을 건너뜁니다. annotation class Stable
  50. @Composable fun val if if else (data: StableData) { previousData

    = ( ) { (data != previousData) { doRecomposition() } { } } } MyComposable ... // nothing to do! isRecompositionNeeded
  51. /** * 클래스나 인터페이스에 @Stable이 있는 경우, 다음 조건들이 반드시

    * 충족해야 합니다. * * 1. 동일한 두 인스턴스끼리 equals() 결과는 항상 true 입니다. * 2. public 프로퍼티의 값이 변경되면 컴포저블 함수가 알 수 있습니다. * 3. 모든 public 프로퍼티의 타입이 @Stable 입니다. * * * 함수에 @Stable이 있을 때는, 동일한 매개변수가 전달되면 항상 동일한 * 결과를 반환함을 의미합니다. */ * 매개변수의 타입이 @Stable이라면, 컴포즈는 해당 매개변수의 인자를 * 비교하여 이전 호출과 동일하면 해당 컴포저블의 리컴포지션을 건너뜁니다. annotation class Stable
  52. /** * 클래스나 인터페이스에 @Stable이 있는 경우, 다음 조건들이 반드시

    * 충족해야 합니다. * * 1. 동일한 두 인스턴스끼리 equals() 결과는 항상 true 입니다. * * 3. 모든 public 프로퍼티의 타입이 @Stable 입니다. * * * 함수에 @Stable이 있을 때는, 동일한 매개변수가 전달되면 항상 동일한 * 결과를 반환함을 의미합니다. */ 2. public 프로퍼티의 값이 변경되면 컴포저블 함수가 알 수 있습니다. * 매개변수의 타입이 @Stable이라면, 컴포즈는 해당 매개변수의 인자를 * 비교하여 이전 호출과 동일하면 해당 컴포저블의 리컴포지션을 건너뜁니다. annotation class Stable
  53. @Composable fun MyComposable(data: UnstableData) { val previousData = ... //

    nothing to do! } if if else ( ) { (data !== previousData) { doRecomposition() } { } } isRecompositionNeeded
  54. @Composable fun val if if else (data: StableData) { previousData

    = ... ( ) { (data != previousData) { doRecomposition() } { } } } MyComposable isRecompositionNeeded // nothing to do!
  55. @Composable fun val if if else (data: UnstableData) { previousData

    = ... ( ) { (data !== previousData) { doRecomposition() } { } } } MyComposable // nothing to do! isRecompositionNeeded
  56. /** * 클래스나 인터페이스에 @Stable이 있는 경우, 다음 조건들이 반드시

    * 충족해야 합니다. * * 1. 동일한 두 인스턴스끼리 equals() 결과는 항상 true 입니다. * 2. public 프로퍼티의 값이 변경되면 컴포저블 함수가 알 수 있습니다. * 3. 모든 public 프로퍼티의 타입이 @Stable 입니다. * * 매개변수의 타입이 @Stable이라면, 컴포즈는 해당 매개변수의 인자를 * 비교하여 이전 호출과 동일하면 해당 컴포저블의 리컴포지션을 건너뜁니다. * * 함수에 @Stable이 있을 때는, 이전과 동일한 매개변수가 제공되면 
 * 항상 동일한 결과를 반환함을 의미합니다. */ annotation class Stable
  57. /** * 클래스나 인터페이스에 @Stable이 있는 경우, 다음 조건들이 반드시

    * 충족해야 합니다. * * 1. 동일한 두 인스턴스끼리 equals() 결과는 항상 true 입니다. * 2. public 프로퍼티의 값이 변경되면 컴포저블 함수가 알 수 있습니다. * 3. 모든 public 프로퍼티의 타입이 @Stable 입니다. * * 매개변수의 타입이 @Stable이라면, 컴포즈는 해당 매개변수의 인자를 * 비교하여 이전 호출과 동일하면 해당 컴포저블의 리컴포지션을 건너뜁니다. * */ * 함수에 @Stable이 있을 때는, 이전과 동일한 매개변수가 제공되면 * 항상 동일한 결과를 반환함을 의미합니다. annotation class Stable
  58. @Stable fun stableCall(): Long = currentTimeMillis() Column { ( currentTimeMillis()

    ) ( count , { count++ } ) () (stableCall()) } (value: Any) { ( value ) } Text Button HorizontalDivider CallArgument Text "ROOT stableCall @ " "count: " "CallArgument @ " ${ } $ $ text = onClick = @Composable fun CallArgument
  59. @Stable fun stableCall(): Long = currentTimeMillis() Column { ( currentTimeMillis()

    ) ( count , { count++ } ) () (stableCall()) } (value: Any) { ( value ) } Text Button HorizontalDivider CallArgument Text "ROOT stableCall @ " "count: " "CallArgument @ " ${ } $ $ text = onClick = @Composable fun CallArgument
  60. @Stable fun stableCall(): Long = currentTimeMillis() Column { ( currentTimeMillis()

    ) ( count , { count++ } ) () (stableCall()) } (value: Any) { ( value ) } Text Button HorizontalDivider CallArgument Text "ROOT stableCall @ " "count: " "CallArgument @ " ${ } $ $ text = onClick = @Composable fun CallArgument
  61. /** * 함수에 @Stable이 있을 때는, 이전과 동일한 매개변수가 제공되면

    * 항상 동일한 결과를 반환함을 의미합니다. */ annotation class fun (): Long = currentTimeMillis() Stable @Stable stableCall
  62. /** * 함수에 @Stable이 있을 때는, 이전과 동일한 매개변수가 제공되면

    * 항상 동일한 결과를 반환함을 의미합니다. */ // @Stable의 특성으로, 매번 다른 값을 반환해도 컴포즈는 항상 // 같은 값을 반환한다고 가정함 annotation class fun (): Long = currentTimeMillis() Stable @Stable stableCall
  63. fun unstableCall(): Long = currentTimeMillis() Column { ( currentTimeMillis() )

    ( count , { count++ } ) () (unstableCall()) } (value: Any) { ( value ) } Text Button HorizontalDivider CallArgument Text "ROOT unstableCall @ " "count: " "CallArgument @ " ${ } $ $ text = onClick = @Composable fun CallArgument
  64. /** * 함수에 @Stable이 있을 때는, 이전과 동일한 매개변수가 제공되면

    * 항상 동일한 결과를 반환함을 의미합니다. */ // @Stable의 특성으로, 매번 다른 값을 반환해도 컴포즈는 항상 // 같은 값을 반환한다고 가정함 annotation class fun (): Long = currentTimeMillis() Stable @Stable stableCall
  65. /** * 함수에 @Stable이 있을 때는, 이전과 동일한 매개변수가 제공되면

    * 항상 동일한 결과를 반환함을 의미합니다. */ // @Stable의 특성으로, 매번 다른 값을 반환해도 컴포즈는 항상 // 같은 값을 반환한다고 가정함 annotation class fun (): Long = currentTimeMillis() Stable @Stable stableCall
  66. Column { (stableCall( )) } ... // 숫자 뿐만 아니라

    모든 상수가 변하지 않는 값 ExpressionArgument 123 @Stable @Composable fun fun stableCall(value: Any): Long = currentTimeMillis() + value.hashCode() (value: Any) { ( value ) } ExpressionArgument Text "ExpressionArgument @ " $
  67. Column { myStaticValue = (stableCall(myStaticValue)) } // 문자열 뿐만 아니라

    모든 상수가 변하지 않는 값 ... val "가나다" ExpressionArgument @Stable @Composable fun fun stableCall(value: Any): Long = currentTimeMillis() + value.hashCode() (value: Any) { ( value ) } ExpressionArgument Text "ExpressionArgument @ " $
  68. enum class MyEnum {
 } { (stableCall(MyEnum. )) } A


    A Column ... ExpressionArgument @Stable @Composable fun fun stableCall(value: Any): Long = currentTimeMillis() + value.hashCode() (value: Any) { ( value ) } ExpressionArgument Text "ExpressionArgument @ " $
  69. class companion object UnstableClass {
 } { (stableCall(UnstableClass.Companion)) } //

    클래스를 상속받게 변경해도 결과는 동일함
 ... Column ExpressionArgument @Stable @Composable fun fun stableCall(value: Any): Long = currentTimeMillis() + value.hashCode() (value: Any) { ( value ) } ExpressionArgument Text "ExpressionArgument @ " $
  70. // @Stable이 없으면 리컴포지션이 생략되지 않음 ... @Stable object StableObject

    { (stableCall(StableObject)) } Column ExpressionArgument @Stable @Composable fun fun stableCall(value: Any): Long = currentTimeMillis() + value.hashCode() (value: Any) { ( value ) } ExpressionArgument Text "ExpressionArgument @ " $
  71. // @Stable이 없으면 리컴포지션이 생략되지 않음 // 타입만 안정하면 결과는

    동일함 (초기화 식은 영향 없음) ... ... @Stable class val StableClass : StableClass = { (stableCall( )) } stableValueProperty stableValueProperty Column ExpressionArgument
  72. // @Stable이 없으면 리컴포지션이 생략되지 않음 // @Stable이 없으면 리컴포지션이

    생략되지 않음 ... ... @Stable @Stable class var StableClass : StableClass = { (stableCall( )) } stableVariableProperty stableVariableProperty Column ExpressionArgument
  73. Column { (listOf( , )) (mapOf( , )) (setOf( ,

    )) ( ) } ... // 원소 타입이 @Stable이어야 함 // Key, Value 타입이 모두 @Stable이어야 함 // 원소 타입이 @Stable이어야 함 // 원소 타입들이 모두 @Stable이어야 함 ExpressionArgument ExpressionArgument ExpressionArgument ExpressionArgument 1 2 1 2 3 4 1 2 1 1 to to to
  74. 
 결과가 변하지 않는 연산 정리 원시 타입 참조 enum

    요소 참조 companion object 객체 참조 @Stable이 달린 object 객체 참조 @Stable이 달렸고, @Stable한 타입의 
 변수나 프로퍼티 참조 반환 값이 불변하다고 정의된 함수들 2025 마법콘
  75. class companion object fun class companion object val ColorFilter {

    { (color: Color): ColorFilter = } } Color { { = } } { (ColorFilter.tint(Color. )) } @Stable @Stable tint ... ... ... Red Red Column ColorFilterArgument
  76. /** * @Immutable이 붙은 클래스는 인스턴스가 생성된 이후로 모든 *

    public 프로퍼티가 변하지 않음을 보장합니다. * * @Immutable은 컴포즈의 컴포지션 과정에서 사용되며, 타입의 값이 * 변하지 않는다는 가정으로 컴포저블 최적화를 가능하게 합니다. */ annotation class Immutable
  77. /** * * @Immutable은 컴포즈의 컴포지션 과정에서 사용되며, 타입의 값이

    * 변하지 않는다는 가정으로 컴포저블 최적화를 가능하게 합니다. */ * @Immutable이 붙은 클래스는 인스턴스가 생성된 이후로 모든 * public 프로퍼티가 변하지 않음을 보장합니다. annotation class Immutable
  78. @Immutable class Column ImmutableClass { (ImmutableClass()) } ... ConstructorArgument @Composable

    fun (value: Any) { ( currentTimeMillis() + value.hashCode() ) } ConstructorArgument Text "ConstructorArgument @ " ${ }
  79. @Immutable class Column ImmutableClass { (ImmutableClass()) } ... ConstructorArgument @Composable

    fun (value: Any) { ( currentTimeMillis() + value.hashCode() ) } ConstructorArgument Text "ConstructorArgument @ " ${ }
  80. @Immutable class Column ImmutableClass { (ImmutableClass()) } ... ConstructorArgument @Composable

    fun (value: Any) { ( currentTimeMillis() + value.hashCode() ) } ConstructorArgument Text "ConstructorArgument @ " ${ }
  81. /** * @Immutable이 붙은 클래스는 인스턴스가 생성된 이후로 * 모든

    public 프로퍼티가 변하지 않음을 보장합니다. */ annotation class class Immutable @Immutable ImmutableClass
  82. @Stable class Column StableClass { (StableClass()) } ... ConstructorArgument @Composable

    fun (value: Any) { ( currentTimeMillis() + value.hashCode() ) } ConstructorArgument Text "ConstructorArgument @ " ${ }
  83. @Stable class Column StableClass { (StableClass()) } ... ConstructorArgument @Composable

    fun (value: Any) { ( currentTimeMillis() + value.hashCode() ) } ConstructorArgument Text "ConstructorArgument @ " ${ }
  84. // 매개변수 타입이 @Stable 이어야 함 ... // ^^^^^^^^^^^^^^^^^^^^ //

    컴파일될 때는 클래스가 사라지고 123이 바로 전달됨 value class val ValueHolder( : Int) { (ValueHolder( )) } value Column ConstructorArgument 123 @Composable fun (value: Any) { ( currentTimeMillis() + value.hashCode() ) } ConstructorArgument Text "ConstructorArgument @ " ${ }
  85. // 매개변수 타입이 @Stable 이어야 함 ... // ^^^^^^^^^^^^^^^^^^^^ //

    컴파일될 때는 클래스가 사라지고 123이 바로 전달됨 value class val ValueHolder( : Int) { (ValueHolder( )) } value Column ConstructorArgument 123 @Composable fun (value: Any) { ( currentTimeMillis() + value.hashCode() ) } ConstructorArgument Text "ConstructorArgument @ " ${ }
  86. 항상 동일한 인스턴스 생성 정리 클래스에 @Immutable이 있고, 모든 인자가


    “결과가 변하지 않는 연산" 이어야 함
 value class라면, 매개변수 타입이 @Stable하고,
 인자가 “결과가 변하지 않는 연산" 이어야 함 2025 마법콘
  87. @Immutable
 class val val BlendModeColorFilter( : Color, : BlendMode) {

    } { ( BlendModeColorFilter(Color. , BlendMode. ) ) } color blendMode Red SrcIn ... ... Column ColorFilterArgument @Composable fun (value: Any) { ( currentTimeMillis() + value.hashCode() ) } ColorFilterArgument Text "ColorFilterArgument @ " ${ }
  88. @Stable은 Input과 Output의 규칙이 명확한 함수에 적합 @Stable은 프로퍼티 값

    비교로 equals()가 구현된 클래스에 적합 (data class는 정의상 @Stable로 간주됨) @Immutable은 한 번 제공한 값을 바꿀 수 없고, 
 그 값에 따라 항상 동일하게 동작하는 클래스에 적합 정리하자면... 2025 마법콘
  89. 성빈 발표 끝! Finish 이 PPT에는 토스팀에서 제공한 토스페이스(이모지 폰트)가

    적용되어 있습니다. 샘플 코드는 빠른 이해를 위해 단순화 및 요약되었습니다. 지성빈 mugba
  90. 잠시 후 “컴포즈를 잘, 그리고 똑똑하게 사용하는 방법을 기대합니다!” “효율적인

    상태 관리 노하우를 얻고싶습니다.” “리컴포지션 마스터하기” “컴포즈 컴파일러를 공부하는 법이 궁금해요” “컴포즈 마법사가 되고싶습니다.” 2025 마법콘