[안드로이드] Compose란 무엇인가?

hee09·2022년 11월 28일
0

명령형 UI와 선언형 UI

Flutter, React native, Switft UI, Android의 Jetpack Compose 등 많은 클라이언트 단에서 선언형 UI를 도입하고 있습니다. Android를 개발하신 분들이라면 아시겠지만 XML 레이아웃을
선언하고 Activity 또는 Fragment에서 코드를 작성하는 익숙한 방법이 있는데, 대체 왜 선언형 UI를 도입하는 걸까요?? 그 이유를 파악하기전에 우선 명령형 UI와 선언형 UI의 개념에 대해서 알아보도록 하겠습니다.


  • 명령형 UI(Imperative UI)

    • 명령형 UI는 현재 가장 많이 사용되고 있는 UI를 작성하는 패러다임입니다.
    • 명령형 UI는 무엇 보다는 어떻게 에 초점을 두고 있습니다.
    • 좋은 예제로 안드로이드의 XML 레이아웃이 있는데, XML 레이아웃 에서는 유저가 보고 상호 작용할 수 있도록 생성되는 위젯 및 컴포넌트를 설계합니다. 즉, 무엇 보다는 어떻게 사용자에게 보여주며 상호작용을 생성할 지 XML 레이아웃을 통해 UI를 선언하는 것입니다.
  • 선언형 UI

    • 선언형 UI는 급부상하고 있는 트렌드로, 개발자는 들어오는 데이터를 기반으로 사용자 인터페이스를 설계할 수 있도록 합니다.
    • 선언형 UI는 어떻게 보다는 무엇에 초점을 두고 있습니다.
    • 아래에서 Jetpack Compose 예시를 보면 알겠지만 데이터를 받아서 코드 단에서 UI를 생성하는데, 이는 어떻게 보다는 무엇에 해당하는 데이터에 초점을 두고 UI를 선언하는 것입니다.

둘의 차이점을 글로만 보면 이해하기가 어렵기에, Flutter 공식 사이트에 나와 있는 예제를 가져와서 자세히 알아보도록 하겠습니다.

명령형 UI

val mainView = MainView()      // mainView(부모 뷰) 생성
mainView.setColor(red)         // mainView 색상 지정
mainView.clearChildren()       // mainView 자식 뷰 clear
val childView = ChildView(...) // chlidView 생성
mainView.add(childView)        // mainView에 childView 추가

선언형 UI

MainView(
   color : red,
   child: ChildView(...)
)

명령형 UI 코드를 보면 사용자에게 어떻게 보여줄 지 초점을 두었기에 단지 부모 뷰와 자식 뷰를 생성하고 속성을 지정하는데 코드가 굉장히 길게 작성되어 있습니다. 이와는 반대로 선언형 UI는 코드가 짧고 직관적이며 immutable 하게 선언되어 있습니다. 이는 무엇에 해당하는 데이터에 초점을 두었기에 저렇게 간단하게 코드를 작성할 수 있는 것입니다.

또한 View가 마치 순수 함수와 같은 형태로 이루어져 있습니다. 즉, 순수 함수와 마찬가지로 같은 인자에 대해서는 항상 똑같은 결과를 리턴하는 것입니다. 사람들은 왜 선언형 UI에 열광할까? 라는 글을 보면 선언형 UI를 잘 표현하는 공식(F(STATE) = VIEW)이 나와 있습니다. Compose로 UI를 구성할 때 저 공식만 기억하며 순수 함수와 같이 사용한다 라고 생각하며 사용하면 좋을 것 같습니다.


선언형 UI를 사용하는 이유

위와 같이 선언형 UI는 코드가 굉장히 직관적이고 짧으며 부수 효과를 없앨 수 있는 순수 함수와 같은 형태로 이루어져 있기에, 많은 개발자들이 선언형 UI를 도입하여 사용하려고 하는 것입니다.
또한 Android의 Compose는 UI 및 Theme를 xml 파일이 아닌 코드 단에서 작성하기에, Kotlin 언어를 통해서 UI 및 비즈니스 로직 등 대부분의 코드를 작성할 수 있게 됩니다. 즉, 통일성이 증가하게 되는 것입니다.


간단한 Compose 예제

Android developer - Jetpack Compose Tutorial에 나와 있는 간단한 예제를 통해 Compose가 무엇인지 알아보도록 하겠습니다. 제가 직접 튜토리얼 코드를 작성하며 이해한 것을 나열하는 것으로, 실제로 Compose를 직접 경험해보고 싶으시다면 위의 링크를 통해 튜토리얼을 경험해 보시는 것을 추천드립니다.


@Composable

@Composable 어노테이션은 Compose를 사용한 앱의 기본적인 구성요소로, 함수 또는 람다에 해당 어노테이션을 작성하면 해당 코드를 UI 트리 또는 계층 구조로의 변형을 나타냅니다. 즉, UI를 구성하는 코드에는 해당 어노테이션을 작성하면 되고, Composable이 작성된 코드는 다른 Composable 코드 안에서만 호출될 수 있습니다(Coroutine의 suspend와 같은 느낌이라 생각).

@Composable
fun SimpleMessage(message: String) {
    Text(
        text = message,
    )
}

Text, Button 등의 View / Column, Row 등의 ViewGroup

View와 ViewGroup을 선언할 때 Text, Column 등을 사용해서 선언할 수 있습니다. 각 클래스의 생성자에는 수 많은 파라미터가 선언되어 있어서, 함수와 마찬가지로 필요한 인자들을 전달해서 사용하면 됩니다. 이를 통해서 수 많은 옵션들을 설정하여 UI를 구성할 수 있습니다.

그리고 ViewGroup 안에 여러 View를 작성하여 계층 구조로 생성할 수 있습니다. 즉, 이전에 언급하였듯이 Composable 어노테이션이 달린 코드는 Composable 코드에서 호출할 수 있으므로 이와 같은 구조가 가능한 것입니다.

@Composable
fun SimpleViewGroup(messageList: List<String>) {
    Column(modifier = Modifier.padding(5.dp)) {
        messageList.forEach {
            SimpleMessage(message = it)
        }
    }
}

Modifier 클래스

Compose의 대부분의 UI 요소들은 modifier 속성을 가지고 있습니다. 해당 속성은 Modifier 클래스를 통해서 설정할 수 있는데, Modifier 클래스는 해당 UI 요소가 어떻게 놓일지, 어떻게 표시될지, 부모 레이아웃 내에서 어떻게 행동할지 등을 나타낼 때 사용할 수 있습니다.

@Composable
fun SimpleMessage(message: String) {
    Text(
        text = message, modifier = Modifier.padding(24.dp)
    )
}

위의 코드는 이전에 만들었던 Text 위젯에 Modifier 클래스를 통해서 padding을 지정하고 있는 코드입니다. 이때, padding 메서드의 인자로 dp 값을 넘겼는데, 이 또한 Compose의 장점인 것 같습니다. 이전에 XML을 통해서 UI를 구성할 때, 코드에서 dp를 사용해야 하면 코드 단에서 계산하여 사용해야 했는데 Compose에서는 변수로 선언되어 있어서 쉽게 사용할 수 있습니다.


@Preview 어노테이션

코드에서 UI를 작성하고 매번 어떻게 UI가 구성되었는지 확인하기 위해서 앱을 빌드하는 것은 굉장한 시간의 낭비입니다. 이를 위해서 Compose에서 @Preview 라는 어노테이션을 통해 UI의 preview를 제공하고 있는데, 해당 어노테이션을 단 메서드는 메서드의 파라미터를 선언하면 안됩니다.

추가적으로 Compose는 빌드하지 않고 UI와 상호작용 하는 기능까지 제공하고 있습니다.

Preview의 오른쪽 위에 보시면 Start Interactive Mode 라는 아이콘이 있습니다. 이를 클릭하면 Preview 단에서 UI 상호 작용이 가능합니다.


Colors, Shape, Theme 등을 Kotlin 코드로 작성

Compose를 선택하고 생성한 프로젝트를 확인하면 코드 안에 위와 같이 theme가 선언되어 있습니다. 각 클래스들은 Kotlin으로 작성되어 있으며, 이를 통해 코드에 통일성을 줄 수 있으며 쉽게 Composable 안에서 해당 코드들을 호출할 수 있습니다.

Theme.kt 파일 코드

private val DarkColorPalette = darkColors(
    primary = Purple200,
    primaryVariant = Purple700,
    secondary = Teal200
)

private val LightColorPalette = lightColors(
    primary = Purple500,
    primaryVariant = Purple700,
    secondary = Teal200
)

@Composable
fun FirstComposeProjectTheme(
    darkTheme: Boolean = isSystemInDarkTheme(), 
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }

    MaterialTheme(
        colors = colors,
        typography = Typography,
        shapes = Shapes,
        content = content
    )
}

결론

이번 글을 통해서 선언형 UI와 명령형 UI의 차이점을 알아보고 Android Compose가 무엇인지 간단하게 알아보았습니다. 앞으로 Compose를 공부하며 프로젝트에서 사용할 때, 이와 같은 개념은 공유하면 좋겠다는 생각이 들면 Compose 시리즈로 작성하겠습니다.

읽어 주셔서 감사합니다.


참조 및 참고
틀린 부분은 댓글로 남겨주시면 수정하겠습니다..!!

Android developer - Why adopt Compose
Android developer - Jetpack Compose Tutorial
Declarative vs Imperative UI in Android
사람들은 왜 선언형 UI에 열광할까?
Flutter - Introduction to declarative UI

profile
되새기기 위해 기록

0개의 댓글