ABOUT ME

-

  • Android) Jetpack Compose를 시작해보자
    Android 2021. 10. 2. 17:38


    What is Compose?

    • UI 개발을 간소화하고 간편하게 할 수 있도록 도와주는 툴킷.
    • 기존의 UI는 명령형 방식이었지만, Compose선언형 방식. UI가 어떻게 보일지에 대한 구현에서 무엇을 보여주면 되는지에 대한 구현으로 변경.
    • 뷰의 상태만 선언하여 구현 부분은 프레임 워크에게 맡기는 방식.
    • 데이터가 변경되면 프레임 워크가 알아서 해당 함수들을  재호출하여 View를 업데이트. 적은 코드, 유지보수, 재사용 및 확장성 용이.

     

    기존의 방식으로 UI를 구성하려면

    • View, ViewGroup에 대한 속성과 정보가 담겨 있는 layout 파일 작성.
    • setContentView() 메서드를 이용해 layout 파일을 보여줍니다.
    • layout 파일 내부의 TextView를 찾아 "Start Jetpack Compose"를 출력.
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val textView = findViewById<TextView>(R.id.textView)
            textView.text = "Start Jetpack Compose"
        }
    }

     

    Compose를 이용해 UI를 구성한다면

    • TextView를 코드 상에서 다음과 같이 그려주고 바로 텍스트의 출력이 가능해집니다.
    • 코드 양이 감소했고, 직관적으로 변했습니다.
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent { 
                Text(text = "Start Jetpack Compose")
            }
        }
    }

     

    Compose를 왜 사용해야 할까?

    • SDK와 UI를 분리 : UI프레임워크와 강하게 묶여 있습니다. androidx 라이브러리는 각각 버전이 업데이트되지만, UI는 SDK와 함께 버전이 업데이트되기 때문에 버전을 변경하기 쉽지 않습니다.
    • UI 재사용을 쉽게 : 기존의 안드로이드에서 UI를 재사용하는 방법은 Custom ViewFragment를 사용하는 것이 대표적입니다. 이 방법은 Boilerplate 코드가 많이 발생하고 수정하는 것이 번거롭습니다.
    • 고성능 발휘 : 레이아웃 상의 하위 요소를 두 번 이상 측정해야 하는 경우가 빈번한데, 이 경우 성능 저하나 앱의 버벅거림이 발생할 수 있습니다. Compose는 하위 요소를 한 번만 측정해야 한다는 규칙을 가지고 있습니다. 만약, 측정값이 여러 번 필요하다면 내장 기능(Intrinsic)을 이용하여 측정할 수 있습니다.

     

     

    Start Jetpack Compose

     

    New Project에서 Empty Compose Activity를 선택하여 프로젝트를 생성했습니다.

    Compose Stable 버전이 1.0으로 출시되었습니다.

     

     

     

     

    생성된 MainActivity를 살펴 보면 기존과 다른 부분들이 보입니다.

     

    • setContent()는 기존의 setContentView() 메서드와 동일한 동작을 하는 확장 함수입니다. 다만, (@Composable) -> Unit 타입의 컴포즈 UI를 구현해줘야 합니다.
    • Theme -> Surface -> TextView를 표현하는 Greeting() 메서드로 구성되어 있습니다.
    • @Composable 어노테이션이 붙은 메서드들은 대문자로 시작하고 있습니다.
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                ComposeExampleTheme {
                    // A surface container using the 'background' color from the theme
                    Surface(color = MaterialTheme.colors.background) {
                        Greeting("Android")
                    }
                }
            }
        }
    }
    
    @Composable
    fun Greeting(name: String) {
        Text(text = "Hello $name!")
    }
    
    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        ComposeExampleTheme {
            Greeting("Android")
        }
    }
    • Greeting() 메서드 내부의 Text() 메서드는 컴포즈 라이브러리에 내장된 text를 그리는 메서드입니다.
    • @Preview 어노테이션을 붙이면 안드로이드 스튜디오의 오른편에서 바로 UI를 확인해 볼 수 있습니다. 또한, @Preview 어노테이션을 붙인 메서드는 인자를 가질 수 없습니다.

     

     

    ComponentActivity()를 상속받고 있습니다.

    • AppCompatActivity는 FragmentActivity를 상속받고 Fragment Activity는 ComponentActivity를 상속받습니다.
    • ComponentActivity에는 Compose 전용 앱에 필요한 모든 부분이 있습니다.

     

    위의 예제를 약간 수정 해보겠습니다.

    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                DivideTextView()
            }
        }
    }
    
    @Composable
    fun Greeting(name: String) {
        Text(text = "Hello $name!", modifier = Modifier.padding(10.dp))
    }
    
    @Composable
    fun DivideTextView() {
        Column {
            Greeting(name = "Compose 1")
            Divider(color = Color.Black)
            Greeting(name = "Compose 2")
        }
    }
    
    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        ComposeExampleTheme {
            DivideTextView()
        }
    }

     

     

     

     

    Column, Row

    • Compose를 사용하는 동안 레이아웃은 ColumnRow로 구성됩니다.
    • ColumnVertical orientation을 갖는 Linear Layout과 동일한 역할을 합니다.
    • 반대로 RowHorizontal orientaion의 역할을 합니다.

     

     

    Modifier

    • Modifier를 이용하면 어떻게 배치하고 그릴지, 행동할지 등을 결정할 수 있습니다.
    • Composable의 크기, 레이아웃, 동작 및 모양 변경, 사용자 입력 처리, 클릭 기능 등.
    • Modifier순서는 결과에 영향을 주기 때문에 중요합니다.
    @Composable
    fun ArtistCard(
        artist: Artist,
        onClick: () -> Unit
    ) {
        val padding = 16.dp
        Column(
            Modifier
                .clickable(onClick = onClick)
                .padding(padding)
                .fillMaxWidth()
        ) {
            Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
            Spacer(Modifier.size(padding))
            Card(elevation = 4.dp) { /*...*/ }
        }
    }

     

    Stateful & Stateless Composable

    • Composable은 상태를 가지고 있는 Stateful Composable과 상태를 가지지 않는 Stateless Composable로 나뉩니다.
    • Stateful Composable은 상태가 변하면 자기 자신과 자식의 Composable을 재구성하고, Stateless Composable은 스스로 재구성하지 못해 부모의 Composable이 재구성되어야 자신이 재구성을 할 수 있습니다.

     

    Compose의 State

    • Compose는 데이터를 UI로 변환하여 사용자에게 보여줍니다. 데이터가 변경되면, UI 역시 변경되어야 하기 때문에 데이터의 상태 변화에 따라 Composable 함수가 다시 호출되어야 합니다.
    • Compose는 수동적인 호출 대신, 데이터가 변경되면 이를 Observing 하고 있다가 자동으로 재호출하여 UI를 그리는 방법을 제공합니다. 이를 재구성 Recomposing이라고 합니다. 이것이 가능한 이유는 custom kotlin complier plugin을 내부적으로 사용하고 있기 때문입니다.
    • Composable은 함수이기 때문에 상태 값을 지역 변수로 저장하면 재구성이 될 때마다 함수가 새로 호출되기 때문에 저장된 상태 값은 유지되지 않습니다.
    • 그래서 상태 값을 계속 유지하려면 remember라는 delegate을 사용합니다. 만약 Configuration 변경이 발생한다면, 상태 값의 유지를 보장하지 못하므로 remeberSaveable을 사용해야 합니다.
    • 그리고 Composable에서 상태를 저장하고, 상태가 변경되었을 때 재구성을 추적하려면 mutableStateOf()를 이용해 MutableState를 생성하여 사용합니다. @Model 어노테이션은 deprecated 되었습니다.

     

    The @Model annotation was deprecated. UsemutableStateOf.

     

     

    Composable에서 MutableState 객체를 선언하는 세 가지 방법

    1. val mutableState = remember { mutableStateOf(default) }
    2. var value by remember { mutableStateOf(default) }
    3. val (value, setValue) = remember { mutableStateOf(default) }

     

     

    State Hoisting

    • State Hoisting Recomposing의 여부를 결정하는 상태를 함수 내부가 아닌 외부로 노출시키는 작업입니다.
    • 자신이 가지고 있는 상태를 부모 Composable로 넘겨주어 자기 자신을 Statelss Composable로 바꾼다는 의미입니다.
    • State Hoisting의 목적은 단방향 데이터 흐름 구조를 가지기 위함입니다.

     

     

    그럼 실제로 Observing 하고 있는지 확인해 보겠습니다.

     

    class MainActivity : ComponentActivity() {
        ...
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                TestComposeState()
            }
        }
    }
    
    @Composable
    fun TestComposeState() {
        val count = remember {
            mutableStateOf(0)
        }
    
        Column {
            DivideTextView()
            CounterButton1(count.value) { newCount -> count.value = newCount }
            CounterButton2(count = count)
        }
    }
    
    @Composable
    fun CounterButton1(count: Int, updateCount: (Int) -> Unit) {
        Log.v(TAG, "FirstButton() is Called #1")
        Button(onClick = { updateCount(count + 1) }) {
            Log.v(TAG, "FirstButton() is Called #2")
            Text(text = "Count#1:$count")
        }
    }
    
    @Composable
    fun CounterButton2(count: MutableState<Int>) {
        Log.v(TAG, "SecondButton() is Called #1")
        Button(onClick = { count.value = count.value + 1 }) {
            Log.v(TAG, "SecondButton() is Called #2")
            Text(text = "Count#2:${count.value}")
        }
    }
    
    ...
    
    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        ComposeExampleTheme {
            TestComposeState()
        }
    }
    • 우선 onCreate()가 호출되면서 4개의 로그가 모두 출력됩니다.
    • 그다음 CounterButton1과 CounterButton2를 한 번씩 클릭했습니다. CounterButton1()의 로그는 모두 출력되는 반면, CounterButton2()의 로그는 내부의 로그만 출력되었습니다.
    • 상태가 변경됨에 따라 필요한 부분만 업데이트되도록 하기 때문에, CounterButton2()의 내부 로그만 출력되었습니다.

     

     

     

     

     

     

     


    Preference

    상태 및 Jetpack Compose

    Compose 기본 구성

    Jetpack Compose 튜토리얼

    반응형

    댓글

Designed by Me.