본문 바로가기

ANDROID/Jetpack

[Jetpack/Compose] Compose 핵심 정리

요즘 세상 핫한 Jetpack Compose의 코드 랩을 마치면서, 내가 느낀 키포인트 네가지에 대해 정리하고 넘어가고자 한다!

1. Declaritive UI

이전의 명령형 UI(Imperative UI)가 현재 구성되어 그려진 View의 상태에 의존적이었다면, 선언형 UI는 현재 View 상태에 대한 의존 없이 철저하게 View를 구성하는 데 사용한 데이터에 기반해 화면을 그린다.

 

더 쉽게 말하자면, 선언형 UI는 UI를 그리는 순간에 대해서만 정의할 뿐, 그려진 UI에 대해서는 생각하지 않는다. 보이는 화면을 바꾸고 싶다면, 보이는 화면이 아닌 보이는 화면을 그린 방법을 살피는 것이다. (이는 Recomposition을 이해하면 더 쉽게 이해될 것이다.)

 

이전에 MVVM 등의 아키텍처 구조에 익숙하다면 쉽게 고개를 끄덕일 만한 좋은 개념이다. View는 애초에 보여야만 하는 목적을 가지고 있어야 하는데, View에 접근하기보다 상태를 Hold 하는 ViewModel 단의 데이터에 따라 View가 수동적으로 움직여야 하는 것이 좀 더 올바르지 않을까? (이 개념을 기존 명령형 UI를 유지하면서 구현해야 했다 보니 xml 레이아웃에 적용하는 양방향 바인딩 등이 등장하게 된 게 아닌가 싶다. Compose를 쓴다면 LiveData를 Compose 코드에서 연결해주는 것으로 작업이 끝나기 때문에 이전 방식에 비해서 번거로움이 확 줄었다 볼 수 있겠다!)

2. Recomposition

선언형 UI 개념의 연장선상에 있는 요소로써, 특정 UI를 업데이트하는 경우 이와 관련된 부분만을 재구성하는 것을 Recomposition이라고 한다. 

 

이를테면 버튼을 클릭할 때마다 텍스트를 바꾸는 Dialog UI가 있다면, Compose는 Dialog 내에서 텍스트 만을 recompose, 다시 그리게 되며, 이외의 요소들은 재생성하지 않게 된다. 아래는 Compose로 개발을 할 때 생각해야 할 중요한 개념들이다.

 

  • Composable function은 코드 순서와는 상관없이 실행이 가능하다.
  • Composable function은 병렬 수행이 가능하다.
  • Recomposition은 최대한 많은 composable function과 lamda function을 스킵하도록 설계되었다.
  • Recomposition은 필요에 따라 취소가 가능하다.
  • 하나의 Composable function은 경우에 따라 매우 빈번하게 재실행될 수 있다.
// Composable 내에서 recompose를 유발하는 value의 조작을 금할 것!
@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
                items++ // Avoid! Side-effect of the column recomposing.
            }
        }
        Text("Count: $items")
    }
}

3. Slot API

모든 Composable은 기본으로 Slot을 가지고 있다. 개발자는 이 Slot에 텍스트를 넣는다던지, 아이콘을 넣는다던지 원하는 요소를 집어넣어 쉽게 커스텀이 가능하도록 한 것이 Slot API다. 이 패턴에 대해 익숙해지고 나면, 직접 구성하는 커스텀 Composable을 극강의 재사용성을 가진 UI 요소로 설계할 수 있을 것이다.

 

Button Composable의 content 파라미터를 주목하자.

@Composable
fun Button(
    modifier: Modifier = Modifier,
    onClick: (() -> Unit)? = null,
    ...
    content: @Composable () -> Unit
)
// Button Slot에 MyImage, Spacer, Text로 구성된 Row Composable을 집어 넣어 나만의 Button을 설계하는 예
Button {
    Row {
        MyImage()
        Spacer(4.dp)
        Text("Button")
    }
}

4. Easy to test, Kotlin friendly, Developer friendly

콜백 지옥을 후대에게는 물려주고 싶지 않아서였을지, Compose는 철저하게 Kotlin 베이스로 개발되었다고 한다. 고로 Kotlin의 coroutine과도 손쉽게 사용 가능하도록 설계되었다. 

// Create a CoroutineScope that follows this composable's lifecycle
val composableScope = rememberCoroutineScope()
Button(
    // ...
    onClick = {
        // Create a new coroutine that scrolls to the top of the list
        // and call the ViewModel to load data
        composableScope.launch {
            scrollState.animateScrollTo(0) // This is a suspend function
            viewModel.loadData()
        }
    }
) { /* ... */ }

 

Compose 코드 랩의 구성을 보면 테스팅에 대한 부분도 상당한데, 그도 그럴 것이 이전의 UI 테스팅 방식에 비해 훨씬 진입장벽이 낮고, 손쉽게 테스트 코드를 짤 수 있겠다는 생각이 들었다. 추가로 Preview나 Interactive 모드를 사용해 구성한 UI를 바로 확인해보거나, 에뮬레이터 없이 직접 조작이 가능하도록 지원해주는 것도 개발자의 편의를 많이 생각해주었다는 생각이 든다.

 

마지막으로 또 한 가지 장점에 대해 언급하자면, Compose는 Theme 등을 활용해 각 팀이 보다 손쉽게 한 서비스의 스타일을 정의하고 통일하면서 Material Design과도 가깝게, 그리고 Night Mode까지 손쉽게 적용할 수 있도록 배려하고 있다는 것이다. 이를 접하고 머리를 탁 치게 되는 깨달음을 얻었다면, 주저하지 말고 디자이너와 팀에게 권하고 있는 자기 자신을 발견하게 되지는 않을까?


종합적으로 Jetpack Compose를 한 바퀴 둘러본 다음 누군가가 나에게 Compose로 안드로이드 앱을 만들겠습니까?라고 묻는다면

 

나는 전적으로 라고 답할 것이다!

 

< 참고 >

 

Compose 이해  |  Jetpack Compose  |  Android Developers

Compose 이해 Jetpack Compose는 Android를 위한 현대적인 선언형 UI 도구 키트입니다. Compose는 프런트엔드 뷰를 명령형으로 변형하지 않고도 앱 UI를 렌더링할 수 있게 하는 선언형 API를 제공하여 앱 UI를

developer.android.com