Jetpack Compose는 Kotlin으로 Android 인터페이스를 구축하기 위한 현대적인 선언형 툴킷입니다. 개발자는 composable 함수를 통해 UI를 설명하고, 툴킷은 변경된 부분만 자동으로 다시 그립니다. Android Developers (2026)에 따르면 Jetpack Compose는 Android 5.0(API 21) 이상에서 실행되며, Material Design 3를 지원하고, 자체 Recomposition 시스템(변경된 위젯만 업데이트하는 스마트 diff 알고리즘) 덕분에 중간급 기기에서 120 FPS를 달성합니다.
주요 내용
Jetpack Compose는 Google이 2019년에 발표하고 2021년에 안정적인 릴리스에 도달한 Android 사용자 인터페이스 구축용 선언형 프레임워크입니다. 기존 View System(XML 레이아웃 + Activity/Fragment)과 달리 Compose는 어노테이션된 Kotlin 함수(@Composable)를 사용합니다. 인터페이스는 완전히 Kotlin으로 설명되며 XML과 코드가 분리되지 않습니다. 이로 인해 XML과 Kotlin의 ID 불일치와 관련된 오류 클래스가 제거되었습니다(type-safe synthetic은 리팩터링에 도움이 되지 않았습니다).
Compose는 View 계층 구조에 종속되지 않는 자체 렌더링 시스템인 Canvas 위에 구축되었습니다. 각 Composable은 View System의 onMeasure/onDraw를 우회하여 Canvas에 직접 그립니다. 이는 복잡한 화면에서 성능 향상을 제공합니다: Google 테스트(2023)에서 200개 요소가 있는 Compose 화면이 RecyclerView + ViewHolder의 유사한 화면보다 40% 더 빠르게 렌더링되었습니다.
Compose에는 minSdk 21(Android 5.0) 및 Kotlin 1.9+가 필요합니다. Compose BOM(Bill of Materials)은 모든 Compose 라이브러리의 버전을 동기화합니다. 프레임워크는 기존 View System 코드와 호환됩니다: Compose는 ComposeView를 통해 XML 레이아웃에 포함되고, 이전 View는 AndroidView를 통해 Compose 계층 구조에 포함됩니다. Google Play Console(2025)에 따르면 Android 5.0+는 활성 기기의 97%를 커버하므로 대부분의 프로젝트에서 호환성은 제한 사항이 아닙니다.
@Composable은 일반 Kotlin 함수를 UI 구성 요소로 변환하는 어노테이션입니다. Composable 함수는 인터페이스의 일부(텍스트, 버튼, 목록)가 어떻게 보여야 하는지 설명합니다. 값을 반환하는 대신 함수는 컴포지션에 UI 컴포넌트를 방출(emit)합니다. 이는 생성자와 유사합니다: 각 함수는 호출될 때 화면에 요소를 추가합니다.
@Composable
fun ProfileCard(name: String, avatarUrl: String) {
Card(
modifier = Modifier.fillMaxWidth().padding(16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Row(verticalAlignment = Alignment.CenterVertically) {
AsyncImage(
model = avatarUrl,
contentDescription = "아바타",
modifier = Modifier.size(48.dp).clip(CircleShape)
)
Spacer(Modifier.width(12.dp))
Text(
text = name,
style = MaterialTheme.typography.titleMedium
)
}
}
}
ProfileCard 함수는 매개변수(name, avatarUrl)를 받고 Card → Row → AsyncImage + Text를 방출합니다. 컴포지션은 단일 패스에서 방출된 컴포넌트의 트리입니다. 매개변수가 변경되지 않은 경우 Compose는 함수 호출을 건너뜁니다(recomposition skip). name만 변경된 경우 Text만 호출되고 나머지 요소는 다시 그려지지 않습니다. 이 지능적인 재구성은 수동 View System 최적화에 비해 Compose의 주요 성능 이점입니다.
Composable 함수는 슬롯(trailing lambda, content: @Composable (() -> Unit))을 적극적으로 사용합니다. 이를 통해 Card, Column, Row와 같은 컨테이너를 만들 수 있으며 콘텐츠가 슬롯에 삽입됩니다. Slot API는 android:layout_gravity와 같은 XML 속성을 대체했습니다. 이제 자식 요소의 위치는 콘텐츠 블록 내의 Kotlin 코드로 설정됩니다.
Compose에서 상태(State)는 시간이 지남에 따라 변경될 수 있는 모든 값입니다. 상태가 변경되면 Compose는 이 상태를 읽는 모든 컴포넌트에 대해 재구성을 예약합니다. 이 메커니즘은 React hooks와 유사합니다: mutableStateOf는 MutableState<T>를 반환하고 .value를 읽으면 현재 컴포지션이 자동으로 변경 사항을 구독합니다.
@Composable
fun CounterExample() {
var count by remember { mutableStateOf(0) }
Column(modifier = Modifier.padding(16.dp)) {
Text("클릭됨: $count")
Button(onClick = { count++ }) {
Text("증가")
}
}
}
@Composable
fun UserScreen(viewModel: UserViewModel) {
val userName by viewModel.userName.collectAsState()
Text("사용자: $userName")
}
remember는 재구성 간에 값을 유지합니다. 그렇지 않으면 UI가 업데이트될 때마다 mutableStateOf가 새로 생성됩니다. collectAsState()는 ViewModel의 StateFlow를 Compose 호환 상태로 변환합니다. 권장 사항 — 화면 수준 상태에는 ViewModel과 StateFlow를 사용하고, 로컬 상태(예: 확장된 카드)에는 mutableStateOf를 사용합니다. 이 구분은 스마트/덤 컴포넌트 원칙을 따릅니다.
State Hoisting은 자식 컴포넌트에서 부모 컴포넌트로 상태를 끌어올리는 패턴입니다. 부모는 매개변수를 통해 값과 콜백을 전달하고, 자식은 변경 시 콜백을 호출합니다. 부모는 mutableStateOf를 보유하고 자식은 매개변수만 보유합니다. 이렇게 하면 컴포넌트를 재사용 및 테스트할 수 있습니다: 동일한 TextField를 모든 데이터 소스와 함께 사용할 수 있습니다.
Modifier는 Composable의 변환(크기, 패딩, 배경, 클릭 처리, 애니메이션, 스크롤)을 설명하는 객체입니다. Modifier는 호출 체인을 통해 적용됩니다: Modifier.fillMaxWidth().padding(16.dp).background(Color.Blue).clickable { }. 각 호출은 속성이 추가된 새 Modifier를 반환합니다. 원본 객체는 변경되지 않습니다.
Modifier의 순서가 중요합니다. Modifier.padding(16.dp).background(Color.Blue)는 패딩이 있는 영역을 채웁니다. Modifier.background(Color.Blue).padding(16.dp)는 내부 사각형을 채우고 패딩은 투명하게 유지됩니다. 메커니즘은 CSS 박스 모델과 유사합니다: padding 먼저 → background는 margin + background처럼 작동; background 먼저 → padding은 background + 내부 패딩처럼 작동. 개발자는 다음만 기억하면 됩니다: padding 먼저 = 외부 여백, padding 나중 = 내부 패딩.
내장 Modifier로 충분하지 않은 경우 Modifier.composed { ... } 또는 Modifier.then()을 통해 사용자 지정 Modifier를 만듭니다. 사용자 지정 Modifier 내에서 레이아웃 측정(Modifier.layout { measurable, constraints -> ... }), 그리기(Modifier.drawWithContent { ... }), 제스처(Modifier.pointerInput { ... })를 사용할 수 있습니다. 예: 클릭 시 펄스 애니메이션을 위한 Modifier — 크기를 측정하고 클릭 시 animateFloatAsState를 통해 스케일 애니메이션을 시작합니다.
애니메이션을 위해 Compose는 animate*AsState(animateFloatAsState, animateColorAsState, animateDpAsState)를 제공합니다. 변경 시 값이 이전 상태와 새 상태 사이에서 애니메이션됩니다. 나타나기/사라지기 애니메이션의 경우 — AnimatedVisibility 및 AnimatedContent에 내장 전환(fade, slide, expand)이 있습니다. 모든 애니메이션은 그래픽 레이어에서 작동하여 불필요한 컴포지션을 트리거하지 않습니다.
Composable 함수는 직접 부수 효과(네트워크 요청, 타이머, 구독)를 수행해서는 안 됩니다. 재구성할 때마다 호출되어 중복 요청이 발생하기 때문입니다. 부수 효과를 위해 Compose는 Effect 함수 제품군을 제공합니다: LaunchedEffect는 컴포지션 진입 시 코루틴을 시작하고 종료 시 취소하며, DisposableEffect는 명시적 정리가 필요한 리소스(센서, BroadcastReceiver)용입니다.
@Composable
fun SensorReader() {
val context = LocalContext.current
var sensorValue by remember { mutableStateOf(0f) }
DisposableEffect(Unit) {
val sensor = registerSensorListener(context) { value ->
sensorValue = value
}
onDispose {
unregisterSensorListener(sensor)
}
}
Text("값: $sensorValue")
}
@Composable
fun UserGreeting(userId: String) {
LaunchedEffect(userId) {
val profile = api.fetchProfile(userId)
// 상태 업데이트
}
}
LaunchedEffect(userId)는 userId가 변경되면 다시 시작됩니다. 이전 코루틴은 취소되고 새 userId로 새 코루틴이 시작됩니다. 이렇게 하면 수동 요청 취소 관리가 필요 없습니다. DisposableEffect(Unit) — 고정 키 Unit을 가진 효과로, 컴포지션 진입 시 실행되고 종료 시 onDispose를 호출합니다. SensorReader는 리스너를 등록하고 화면을 떠날 때 구독을 취소합니다 — 누수 위험 없이.
컴포지션 진입 시가 아니라 이벤트(버튼 클릭)에서 코루틴을 시작해야 하는 경우 rememberCoroutineScope()를 사용합니다. Composable 수명 주기에 바인딩된 CoroutineScope를 반환하며 DisposableEffect가 필요 없습니다. 예: 버튼 클릭 시 네트워크 요청 시작 — scope.launch { viewModel.loadData() }.
Compose와 View System 중 선택은 2026년 Android 개발자의 주요 아키텍처 질문입니다. 두 기술 모두 Google에서 지원하지만 Compose는 Google이 리소스를 투자하는 주요 방향입니다. View System은 중요한 수정만 받고 발전하지 않습니다. 차이는 구문, 상태 관리, 성능 및 개발 시간에서 나타납니다.
| 측면 | Jetpack Compose | View System |
|---|---|---|
| UI 설명 | Kotlin @Composable 함수 | XML 레이아웃 + Activity/Fragment |
| 상태 | mutableStateOf, StateFlow, 자동 다시 그리기 | findViewById, 수동: setText, notifyDataSetChanged |
| 성능 | 지능형 재구성, Canvas 렌더링 | View 계층 구조, measure/layout/draw |
| 애니메이션 | animate*AsState, AnimatedVisibility, 내장 | ValueAnimator, ObjectAnimator, Transition |
| 호환성 | minSdk 21, ComposeView/AndroidView 브리지 | 모든 버전, 모든 |
| APK 크기 | Compose에 +3–5MB | 오버헤드 없음 |
새 프로젝트의 경우 Google은 Jetpack Compose를 UI 개발 표준으로 권장합니다. View System은 2021년 이전에 작성된 코드 유지 관리와 최소 APK 크기가 중요한 경우(예: 보급형 기기를 위한 신흥 시장)를 위해 남아 있습니다. Compose는 선언형 구문과 내장 애니메이션 덕분에 View System에 비해 UI 코드 양을 30~50% 줄입니다.
자주 묻는 질문
네, XML 레이아웃의 ComposeView를 통해 가능합니다. Compose 종속성을 추가하고 화면 또는 일부를 ComposeView { MyComposable() }으로 감쌉니다. 마이그레이션은 화면별로 진행됩니다.
이유는 상태가 너무 높게 끌어올려졌거나 변경 가능한 객체가 사용되었기 때문입니다. 해결 방법: 파생 데이터에는 derivedStateOf, 안정적인 참조에는 remember를 사용하세요.
LazyColumn(RecyclerView와 유사)을 사용하세요. 스크롤에 따라 요소가 생성 및 재사용됩니다. 다양한 셀 유형이 있는 복잡한 목록의 경우 — LazyColumn { items(items, key = { it.id }) { ... } }.
아니요, Compose로 직접 시작할 수 있습니다. View System에 대한 지식은 레거시 코드 유지 관리에 도움이 되지만 Compose는 자체 문서와 패턴을 가진 독립적인 생태계입니다.
네, Material 3는 2023년부터 Compose의 표준 테마입니다. implementation("androidx.compose.material3:material3")을 통해 추가됩니다. Material 2는 더 이상 사용되지 않습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.