
시작하기에 앞서
최근 Nature Album 프로젝트를 통해 Compose를 처음 학습하고 프로젝트에 적용하며 많은 것을 배울 수 있었다. 이번 글은 프로젝트에서 사용한 Compose를 제대로 알기 위해 정리 차원에서 작성되었다.
오늘은 아래와 같은 사항들을 살펴보겠다.
- Compose가 무엇인지 살펴보며 View System과 어떻게 다른지 알아보자.
- Compose는 Kotlin으로 구현된다. 둘 사이 어떤 관계가 있을까?
- Compose는 함수로 그려진다. 이에 대한 패러다임을 알아보자.
Jetpack Compose의 개요
Jetpack Compose는 Android의 선언형 UI 툴킷으로, Kotlin 기반의 직관적이고 간결한 API를 제공하여 기존 View 시스템 대비 코드 간소화 및 유지보수성을 크게 개선한 도구이다.
Compose란 무엇인가?
Jetpack Compose는 Android의 선언형 UI 툴킷으로, Kotlin 기반의 직관적이고 간결한 API를 통해 XML 파일 없이 더 적은 코드로 UI를 빌드할 수 있는 도구이다. 상태(State) 관리에 따른 자동 업데이트인 Recomposition을 지원하며, 기존 코드와의 호환성을 제공하여 점진적인 도입이 가능하다.
기존 View 시스템에서는 XML 파일로 UI를 정의하고, 이를 코드에서 연결(bind)하거나 동적으로 변경해야 했다. 반면, Compose는 UI와 상태를 함수로 선언적으로 작성하고, 상태 변화에 따라 UI가 자동으로 갱신되므로 불필요한 코드가 줄어든다. 예를 들어, ViewBinding과 DataBinding에서 생성되는 binding 클래스 파일이 필요하지 않아 코드가 간결해진다.
기존 View 시스템과 Compose의 차이
Jetpack Compose는 Android의 선언형 UI 툴킷으로, 기존 View 시스템의 명령형 UI와 본질적으로 다른 프로그래밍 패러다임을 사용한다. 기존 View 시스템은 findViewById()와 같은 메서드를 사용해 UI 계층을 수동으로 관리하며, 상태 변화에 따라 뷰의 속성을 명시적으로 업데이트해야 한다. 이는 다수의 View를 관리할 때 복잡성과 오류 가능성을 증가시킨다. 반면, Compose는 상태(State)에 따라 필요한 UI를 자동으로 재구성(Recomposition)하며, 선언적으로 "무엇(What)"을 그릴지에만 집중할 수 있다.
정리하자면 아래와 같다.
1. 기존 View 시스템의 특징
- UI를 XML 레이아웃 파일로 정의하고, findViewById()를 통해 View를 참조한다.
- 상태 변경 시 UI를 명시적으로 갱신해야 하며, 복잡성이 증가한다.
- View 계층 구조를 직접 관리해야 하므로 유지보수가 어렵다.
2. Compose의 특징
- UI를 Kotlin 함수로 정의하며, XML 파일 없이 코드를 작성한다.
- 상태(State)에 따라 필요한 UI만 자동으로 갱신한다.
- View 계층 구조를 자동으로 관리하며, 선언형 프로그래밍 방식을 따른다.
선언형 프로그래밍 패러다임
Jetpack Compose는 선언형 프로그래밍 패러다임을 채택하여 UI를 작성한다. 선언형 프로그래밍은 무엇을 할지(What)에 초점을 맞추며, 기존 명령형 프로그래밍이 사용하는 절차적 접근법과 차별화된다.
선언형 UI 모델의 동작 방식
- 기존 View 시스템
- View 계층 구조를 직접 관리하며, 상태를 업데이트하기 위해 명령형 메서드(setText, setImageResource)를 호출한다.
- 상태 관리와 UI 갱신이 분리되어 있어 오류 발생 가능성이 높다. (예: 삭제된 노드를 참조)
- Compose의 선언형 UI
- 상태(State) 기반으로 UI를 구성하며, 상태가 변경되면 필요한 부분만 다시 그린다.
- View 계층 구조를 수동으로 관리할 필요가 없으며, 상태와 UI 동기화가 자동으로 이루어진다.
Recomposition과 최적화
Compose는 상태 변화에 따라 UI 전체를 다시 생성하는 개념을 기반으로 하지만, 실제로는 변경된 부분만 다시 그리는 Recomposition을 통해 성능을 최적화한다. Recomposition은 다음과 같은 단계를 거친다:
- Composition 단계: 상태를 읽어 UI를 정의한다.
- Layout 단계: UI의 크기를 측정하고 위치를 결정한다.
- Drawing 단계: 변경된 부분만 캔버스에 렌더링한다.
Compose의 Recomposition은 기존 View 시스템의 invalidate()나 requestLayout() 호출보다 효율적이며, 성능 최적화를 자동으로 처리한다.
Recomposition 예제:
@Composable
fun CounterApp() {
var count by remember { mutableStateOf(0) }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Count: $count", fontSize = 24.sp)
Button(onClick = { count++ }) {
Text("Increase")
}
}
}
위 예제에서 count의 상태가 변경되면 Text와 Button 중 필요한 부분만 다시 그려진다.
Recomposition에 대한 자세한 내용은 추후 다른 포스팅에서 다뤄보겠다.
Kotlin과 Compose
Compose는 Kotlin의 함수형 프로그래밍 특성을 활용하여 선언형 UI를 구현한다. Kotlin은 정적 타입 지정, 타입 추론, 함수형 프로그래밍 지원, 널 안전성, 코루틴과 같은 현대적인 프로그래밍 언어의 장점을 제공한다.
Kotlin과 Compose의 관계
- 함수형 프로그래밍 지원
- Kotlin은 일급 함수(First-Class Function)와 고차 함수(Higher-Order Function)를 지원한다.
[참고 링크 : https://kotlinlang.org/docs/lambdas.html]
① Kotlin에서 함수는 일급시민(first-class citizen)이다.
- Kotlin에서 함수는 일급 시민(first-class)으로 취급된다.
이는 함수가
① 변수나 데이터 구조에 저장될 수 있고,
② 다른 고차 함수(Higher-Order Function)에
ⓐ인자로 전달되거나 ⓑ반환값으로 사용될 수 있음을 의미한다.
또한, 함수에 대해 다른 비-함수 값에서 가능한 모든 작업을 수행할 수 있다.
이를 가능하게 하기 위해, Kotlin은 정적 타입 언어로서 함수를 표현하기 위한 함수 타입(function type) 계열을 제공하며, 람다 표현식과 같은 특화된 언어 구문을 지원한다.
② 일급 시민의 특성 덕분에 고차함수를 만들기 편리하다.
고차 함수(Higher-Order Functions)란?
- 함수를 인자로 받거나, 함수를 반환하는 함수를 말한다.
-
- Compose는 이러한 특성을 활용하여 UI를 함수로 정의하고 상태 변화에 따라 UI를 갱신한다.
- 일급 함수와 선언형 UI
- Kotlin에서 함수는 변수에 할당되거나 다른 함수의 인자로 전달될 수 있다.
- Compose는 이 특성을 사용하여 UI를 함수 기반으로 선언적으로 표현한다.
- Kotlin의 간결한 문법
- Kotlin의 람다 표현식과 타입 추론 덕분에 Compose 코드는 직관적이고 간결하다.
- 람다 함수(Lambda Function)란?
- 람다 함수는 이름이 없는 익명 함수(Anonymous Function)로, 코드에서 간결하고 즉석에서 사용하기 위해 작성된 함수이다. Kotlin을 포함한 여러 현대 프로그래밍 언어에서 지원하며, 주로 고차 함수의 인자로 전달하거나 콜백을 구현할 때 사용된다.
- 람다 함수(Lambda Function)란?
- Kotlin의 람다 표현식과 타입 추론 덕분에 Compose 코드는 직관적이고 간결하다.
잠깐 체크! 함수형 프로그래밍과 객체지향 프로그래밍
함수형 프로그래밍과 객체지향 프로그래밍
Compose는 Kotlin의 함수형 프로그래밍 특성을 적극 활용한다. 함수형 프로그래밍은 상태와 부작용을 최소화하고, 순수 함수(Pure Function)를 중심으로 데이터를 처리하는 패러다임이다.
함수형 프로그래밍이란?
- 함수형 프로그래밍(Functional Programming)은 함수를 일급 시민으로 다루고, 상태와 부작용을 최소화하며, 순수 함수(Pure Function)를 중심으로 프로그래밍을 구성하는 패러다임이다.
- 주요 특징:
- 순수 함수(Pure Function): 동일한 입력값에 대해 항상 동일한 출력값을 반환하며, 외부 상태를 변경하지 않는다.
- 불변성(Immutability): 데이터 변경이 아닌, 새로운 데이터를 생성하여 작업을 수행한다.
- 고차 함수(Higher-Order Function): 함수를 인자로 전달하거나 반환할 수 있다.
- 람다 함수(Lambda Function): 간결한 표현으로 익명 함수를 사용할 수 있다.
- 선언형 프로그래밍: 무엇을 할지(What)에 초점을 맞춘다.
예제 (Kotlin):
val numbers = listOf(1, 2, 3, 4)
val squared = numbers.map { it * it } // 선언형 방식
println(squared) // [1, 4, 9, 16]
객체지향 프로그래밍이란?
- 객체지향 프로그래밍(Object-Oriented Programming, OOP)은 객체(Object)와 클래스(Class)를 중심으로 데이터와 행위를 캡슐화하여 프로그램을 구성하는 패러다임이다.
- 주요 특징
- 캡슐화(Encapsulation): 데이터와 메서드를 객체 안에 묶어서 관리.
- 상속(Inheritance): 부모 클래스의 속성과 메서드를 자식 클래스가 재사용.
- 다형성(Polymorphism): 동일한 메서드 이름으로 다양한 동작을 수행.
- 추상화(Abstraction): 불필요한 세부 정보를 숨기고 중요한 부분만 표현.
예제 (Kotlin):
open class Shape {
open fun area(): Double = 0.0
}
class Circle(val radius: Double) : Shape() {
override fun area(): Double = Math.PI * radius * radius
}
val circle = Circle(3.0)
println(circle.area()) // 28.27
함수형 프로그래밍과 객체지향 프로그래밍의 차이점
| 특징 | 함수형 프로그래밍 | 객체지향 프로그래밍 |
| 중심 개념 | 함수와 순수 함수 | 객체와 클래스 |
| 데이터 상태 | 불변성 강조 (Immutable) | 가변성 허용 (Mutable) |
| 프로그래밍 방식 | 선언형(What): 작업의 결과에 초점 | 명령형(How): 작업의 절차에 초점 |
| 코드 재사용 | 고차 함수와 함수 조합 | 상속과 다형성을 활용한 코드 재사용 |
| 부작용 관리 | 부작용 최소화 | 상태 변경과 부작용이 일반적 |
| 사용 예 | 데이터 변환(Map, Reduce, Filter 등), 비동기 작업 | UI 설계, 객체 간의 관계 표현 |
- 함수형 프로그래밍은 순수 함수와 불변성을 강조하며, 선언형 방식으로 데이터를 처리하고 부작용을 최소화한다.
- 객체지향 프로그래밍은 객체와 클래스를 중심으로 데이터와 행위를 캡슐화하여 명령형 방식으로 작업을 수행한다.
- Kotlin은 함수형 프로그래밍과 객체지향 프로그래밍을 모두 지원하며, 두 가지 패러다임을 조합하여 개발자 요구에 맞게 활용할 수 있다.
Composable 함수의 역할과 특징
Composable 함수는 Compose의 기본 구성 요소로, UI를 정의하고 상태 변화에 따라 필요한 부분만 다시 그리는 역할을 한다.
즉, Compose UI는 함수로 구성되고 그려진다.
Composable 함수의 특징
- Composable 함수로 UI 정의
- Composable 함수는 UI를 함수로 정의하며, @Composable 애노테이션이 필요하다.
- 상태 변화에 따라 필요한 UI 부분만 Recomposition을 통해 갱신된다.
- 상태 기반 UI 갱신
- 상태(State)를 읽어 UI를 구성하며, 상태가 변경되면 Compose가 자동으로 UI를 갱신한다.
- 효율적인 렌더링
- Compose는 상태 변화로 인해 영향을 받은 Composable 함수만 다시 호출하여 성능을 최적화한다.
@Composable 어노테이션
- 역할
- 컴파일러에 해당 함수가 Compose의 UI 선언 함수임을 알린다.
- Recomposition과 최적화를 가능하게 한다.
- 필요성
- Compose 컴파일러는 @Composable 어노테이션이 없는 함수는 UI 트리에서 호출할 수 없다.
- Composable 내에서 Composable만 호출 가능한 이유는, Compose 컴파일러가 UI 상태와 Recomposition 트리를 관리하며, 이 과정에서 상태 변경을 추적하고 필요한 UI만 다시 그리도록 최적화하기 때문이다.
- Composable 함수로 처리하지 않는 동작(예: 네트워크 요청)은 Side Effect로 관리하며, 이를 위해 LaunchedEffect, rememberCoroutineScope 등을 활용한다.
- 상태 기반 UI 동기화와 효율적인 Recomposition을 위해 필수적이다. Recomposition은 상태 변경에 따라 필요한 UI만 다시 렌더링하도록 최적화된 Compose의 핵심 메커니즘이다.
- Compose 컴파일러는 @Composable 어노테이션이 없는 함수는 UI 트리에서 호출할 수 없다.
Compose와 View 시스템의 비교
UI 트리 구성 방식
- Compose: 함수 기반으로 UI를 정의하며 상태에 따라 자동으로 UI를 갱신한다.
- 자동이라니 어떻게 자동일까?
- 어떻게 자동으로 갱신하는지는 Recomposition과 관련하여 추후 다른 포스팅을 통해 알아보자.
- View 시스템: XML 파일과 명령형 코드로 UI를 구성하며, 상태 변경 시 명시적으로 UI를 업데이트해야 한다.
렌더링 방식
- Compose: 상태 변화 시 필요한 부분만 Recomposition을 통해 다시 그린다.
- View 시스템: 상태 변경 시 전체 View 계층 구조를 다시 그려야 할 가능성이 크다.
유지보수성
- Compose: 상태와 UI가 강하게 연결되어 유지보수가 쉽다.
- View 시스템: 상태와 UI가 분리되어 있어 복잡성이 증가한다.
마무리
Jetpack Compose는 선언형 UI 모델을 기반으로 기존 View 시스템보다 간결하고 효율적인 UI 작성 방식을 제공한다. Kotlin의 함수형 프로그래밍 특성을 적극 활용하여 상태 기반 UI 갱신을 자동화하며, 기존 View 시스템의 복잡성과 오류 가능성을 줄인다.
다음 글에서는 Compose의 상태 관리와 Recomposition 메커니즘에 대해 다뤄보겠다.
참고로 선언형 프로그래밍 패러다임에 대해 좀 더 알고싶다면 아래의 공식문서를 확인하도록 하자.
선언형 프로그래밍 패러다임
지금까지 Android 뷰 계층 구조는 UI 위젯의 트리로 표시할 수 있었습니다. 사용자 상호작용 등의 이유로 인해 앱의 상태가 변경되면, 현재 데이터를 표시하기 위해 UI 계층 구조를 업데이트해야 합니다. UI를 업데이트하는 가장 일반적인 방법은 findViewById()와 같은 함수를 사용하여 트리를 탐색하고 button.setText(String), container.addChild(View) 또는 img.setImageBitmap(Bitmap)와 같은 메서드를 호출하여 노드를 변경하는 것입니다. 이러한 메서드는 위젯의 내부 상태를 변경합니다.
뷰를 수동으로 조작하면 오류가 발생할 가능성이 커집니다. 데이터를 여러 위치에서 렌더링한다면 데이터를 표시하는 뷰 중 하나를 업데이트하는 것을 잊기 쉽습니다. 또한 두 업데이트가 예기치 않은 방식으로 충돌할 경우 잘못된 상태를 야기하기도 쉽습니다. 예를 들어 업데이트가 UI에서 방금 삭제된 노드의 값을 설정하려고 할 수 있습니다. 일반적으로 업데이트가 필요한 뷰의 수가 많을수록 소프트웨어 유지관리 복잡성이 증가합니다.
지난 몇 년에 걸쳐 업계 전반에서 선언형 UI 모델로 전환하기 시작했으며, 이에 따라 사용자 인터페이스 빌드 및 업데이트와 관련된 엔지니어링이 크게 간소화되었습니다. 이 기법은 처음부터 화면 전체를 개념적으로 재생성한 후 필요한 변경사항만 적용하는 방식으로 작동합니다. 이러한 접근 방식은 스테이트풀(Stateful) 뷰 계층 구조를 수동으로 업데이트할 때의 복잡성을 방지할 수 있습니다. Compose는 선언형 UI 프레임워크입니다.
화면 전체를 재생성하는 데 있어 한 가지 문제는 시간, 컴퓨팅 성능 및 배터리 사용량 측면에서 잠재적으로 비용이 많이 든다는 것입니다. 이 비용을 줄이기 위해 Compose는 특정 시점에 UI의 어떤 부분을 다시 그려야 하는지를 지능적으로 선택합니다. 이는 재구성에 설명된 대로 UI 구성요소를 디자인하는 방식에 몇 가지 영향을 미칩니다.
https://developer.android.com/develop/ui/compose/mental-model?hl=ko&utm_source=chatgpt.com
Compose 이해 | Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose 이해 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Jetpack Compose는 Android를 위한 현대적인 선언
developer.android.com