[Android] Jetpack Compose로 재사용성 높은 UI 작성하기


개요

모바일 개발 프레임워크 및 플랫폼 중 유일하게 독특한 UI 작성 방식을 갖고 있는 플랫폼이 있습니다. 바로 Android입니다. iOSFlutter와는 다르게 xml을 통해 레이아웃과 화면 요소를 구성하고 View에 바인딩하는 형태입니다. 그런데 이번에 안드로이드에서도 네이티브 언어인 Kotlin을 통해 컴포넌트 방식으로 UI를 작성할 수 있는 Android Jetpack Compose가 마침내 stable 버전으로 배포되었습니다. 기존의 방식에 비해서 Compose가 갖는 특징과 이점에 대해 살펴봅시다.


Android Jetpack Compose

구글에서는 Jetpack Compose가 Android에서 UI 개발을 간소화하고, 가속화할 수 있다고 합니다. Jetpack Compose 공식 페이지의 메인에서 하나의 코드와 그에 대한 예시 화면을 소개하고 있는데, 이것만으로 Compose의 강력함을 알 수 있었습니다.

0

좌측의 코드는 실제 Jetpack Compose를 사용한 코드이고, 오른쪽의 화면은 실제 해당 코드로 빌드되는 UI입니다. 위 예시를 보고 느낀 점은 코드가 굉장히 직관적이고 단순하며, 애니메이션을 굉장히 쉽게 구현할 수 있다는 것이었습니다. 기존의 안드로이드 UI 작성 방식으로는 상상할 수 없는 일이었습니다.

flutter를 다뤄본 경험이 있는 저로서는 플러터와 코드 작성 방식이 거의 유사하다는 생각이 들었습니다. 플러터 또한 구글에서 개발한 프레임워크이고, 머티리얼 디자인을 기본으로 지원하기 때문에 CardColumn과 같이 매우 익숙한 요소들을 확인할 수 있었습니다. 심지어 위 코드에서 보이는 AnimatedVisibility는 동일한 이름과 기능으로 플러터에서도 존재합니다. 또, mutableStateOf()를 보아 플러터와 마찬가지로 state라는 개념을 통해 UI를 업데이트 한다는 것을 알 수 있습니다. 이 개념은 React와 같이 웹 프론트엔드를 접해보신 분들에게도 익숙하실 겁니다.

구글에서는 Android Jetpack Compose의 장점을 다음과 같이 정리합니다.

Jetpack Compose 장점

  • 적은 코드로 더 많은 작업을 할 수 있어 유지 보수가 쉽다.
  • 직관적으로 UI를 구성할 수 있고 앱 상태 변경시 자동으로 UI 업데이트 한다.
  • 기존의 Activity 개념과 호환되며 Android Studio에서 실시간 미리보기와 같은 기능을 지원한다.
  • 기본적으로 머티리얼 디자인, 다크 테마, 애니메이션 등을 지원한다.


재사용 가능한 UI 작성

머티리얼 디자인의 기본 지원과 풍부한 애니메이션도 물론 좋지만, 제가 생각하는 Jetpack Compose의 최대 장점은 바로 안드로이드에서 재사용성이 높은 UI 코드를 작성할 수 있게 하는 것입니다. Jetpack Compose는 이름 그대로 Composable한 UI를 작성할 수 있기 때문에 하나의 UI 컴포넌트를 작성하면 여러 화면에서 재사용할 수 있습니다. 실제로 Jetpack Compose를 이용하여 직접 진행했던 프로젝트의 일부 코드를 보며 살펴보겠습니다.

1

위 UI는 단계별로 정보를 입력하는 과정에서 화면 하단에 BottomBar로써 고정되어 다음 단계나 이전 단계로 넘어가는 상호작용을 담당합니다. 이전 단계가 존재하지 않는 첫번째 단계, 이전과 다음이 모두 존재하는 중간 단계, 그리고 마지막 단계에서 완료 버튼이 나타나는 UI로 총 3가지 종류가 있고 각각은 다음 버튼의 활성화 여부에 따라 2가지 상태를 갖습니다. 이렇게 총 6가지의 UI를 Jetpack Compose로 개발하면 단 하나의 UI 코드로 표현할 수 있습니다. 코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Composable
fun BottomStepBar(
    showPrevious: Boolean,
    onPreviousClick: () -> Unit,
    enabledNext: Boolean,
    onNextClick: () -> Unit,
    showDone: Boolean,
    onDoneClick: () -> Unit
) {
    Surface(
        elevation = 7.dp,
    ) {
        Row(
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween,
            modifier = Modifier
                .fillMaxWidth()
                .padding(18.dp)
        ) {
            if (showPrevious) {
                OutlinedButton(
                    onClick = onPreviousClick,
                    contentPadding = PaddingValues(vertical = 12.dp, horizontal = 20.dp),
                    shape = MaterialTheme.shapes.medium,
                    modifier = Modifier.weight(1f)
                ) {
                    Text("이전", style = MaterialTheme.typography.subtitle1)
                }
                Spacer(modifier = Modifier.width(12.dp))
            }
            Button(
                onClick = if (showDone) onDoneClick else onNextClick,
                enabled = enabledNext,
                shape = MaterialTheme.shapes.medium,
                contentPadding = PaddingValues(vertical = 12.dp, horizontal = 20.dp),
                modifier = Modifier.weight(1f)
            ) {
                Text(
                    if (showDone) "완료" else "다음",
                    style = MaterialTheme.typography.subtitle1
                )
            }
        }
    }
}

BottomStepBar 컴포넌트에 UI의 상태를 나타내는 매개변수들을 받아서 UI를 실시간으로 업데이트 시켜줄 수 있습니다. 또한 상태 뿐만 아니라, 버튼 클릭 시의 동작을 람다 형식으로 Kotlin의 고차 함수(High-order function)를 이용하여 전달할 수 있습니다. 따라서 위와 같이 한 번 만들어진 컴포넌트로 여러 화면에서 각기 다른 stateonClick을 넘겨줌으로써 UI를 재사용할 수 있습니다.


마치며

기존에 flutter로 복잡한 UI를 개발한 경험이 있었는데 이때의 편리함이 아주 인상적이었습니다. 그래서 Android Jetpack Compose가 정식으로 출시되자마자 사용해보았습니다만, 안드로이드 UI 개발에 완전히 새로운 패러다임이 찾아왔음을 느꼈고, 앞으로는 당연하게 Jetpack Compose로 개발하게 되지 않을까 생각합니다. 실제로 Jetpack Compose를 적용해봤던 프로젝트에서 아직 인터넷에 많은 자료가 있지 않음에도 불구하고 빠르고 쉽게 강력한 UI를 작성할 수 있었습니다.

다음 포스트에서는 MVVM 아키텍쳐에서 ViewModelState와 함께 Jetpack Compose를 사용해보는 내용을 다뤄보도록 하겠습니다.

0%