Composable Function은 Jetpack Compose에서 사용자 인터페이스의 기본 단위로, 화면의 일부가 어떻게 보이고 동작해야 하는지 정의합니다. 각 함수는 @Composable 어노테이션으로 표시되며, Compose가 종속성을 추적하고 데이터가 변경될 때 UI를 자동으로 재구축할 수 있는 특별한 컨텍스트에서 실행됩니다. Google Android Developers, 2026에 따르면, Composable 함수의 올바른 구축은 애플리케션 성능과 재구성 효율성에 직접 영향을 미칩니다.
주요 포인트
Composable Function은 @Composable 어노테이션이 있는 Kotlin 언어의 함수로, 선언적 방식으로 사용자 인터페이스의 일부를 설명합니다. Java 코드나 XML 마크업을 통해 View 객체를 생성하고 구성하는 대신, 개발자는 데이터의 각 상태에 따라 UI가 어떻게 보일지를 간단히 작성합니다.
Composable 함수와 전통적인 Android View 시스템의 주요 차이점은 업데이트 모델에 있습니다. 고전적인 접법에서는 개발자가 수동으로 findViewById를 호출하고, setText를 통해 텍스트를 변경하고, setVisibility를 통해 가시성을 관리했습니다. Composable Function은 이러한 루틴에서 해방됩니다: 데이터가 변경되면 시스템 자체가 어느 함수를 재구성해야 하는지 판단하고 해당 함수만 실행합니다.
Kotlin 컴파일러는 @Composable 어노테이션을 처리할 때 함수를 컴포지션 메커니즘에 통합하는 추가 코드를 생성합니다. 이 코드에는 슬롯(현재 UI 트리에서 각 Composable 함수의 상태와 파라미터를 저장하는 특별한 메모리 셀) 읽기 및 쓰기가 포함됩니다. 이 통합 덕분에 Compose는 어느 함수가 어느 데이터에 종속하는지 알 수 있습니다.
Composable 함수의 구문은 극가로 간결합니다: fun 키워드 앞에 @Composable을 추가하면 됩니다. 함수는 임의의 파라미터를 허용하고, 본문에 다른 Composable 호출을 포함하며, 조건부 UI 렌더링을 위해 Kotlin 구조(조건, 루프, when 표현식)를 사용할 수 있습니다.
@Composable
fun ProductItem(
product: Product,
modifier: Modifier = Modifier,
onAddToCart: () -> Unit
) {
Card(modifier = modifier.padding(8.dp)) {
Row(modifier = Modifier.fillMaxWidth().padding(12.dp),
verticalAlignment = Alignment.CenterVertically) {
Column(modifier = Modifier.weight(1f)) {
Text(text = product.name, style = MaterialTheme.typography.titleMedium)
Text(text = "${product.price}", color = MaterialTheme.colorScheme.primary)
}
Button(onClick = onAddToCart) {
Text("장바구니에 추가")
}
}
}
}
이 예제에서 ProductItem Composable 함수는 Product 객체, 수정자 및 콜백을 허용합니다. 세 파라미터 모두 불변이므로 재구성 중 예측 가능한 동작을 보장합니다. 수정자는 기본값이 있는 파라미터로 전달되며, 이는 호출자가 패딩과 크기를 커스터마이즈할 수 있도록 하는 표준 파틀입니다.
Composable 함수 내부에서는 빌트인 Material Design 컴포넌트(Text, Button, Card, TextField)나 기본 프리미티브(Canvas, Layout)가 사용됩니다. 각 컴포넌트는 외관과 동작을 구성하는 파라미터와 modifier 파라미터를 통한 하나 이상의 수정자를 허용합니다.
수정자는 컴포넌트의 크기, 위치, 이벤트 처리 및 외관을 변경하는 함수 체인입니다. 체인에서 수정자의 순서는 중요합니다: clickable.semantics는 semantics.clickable과 다른 방식으로 동작하고, padding.background는 패딩을 포함한 영역과 배경을 모두 칠하여 설계에서 중요합니다.
Composable 함수 내부에서는 if와 when 조건을 사용하여 UI 일부를 조건부로 렌더링하거나, for 루프를 사용하여 동적 리스트를 처리할 수 있습니다. Kotlin은 완전한 프로그래밍 언어이기 때문에 이러한 구조는 자연스럽게 동작합니다. 그러나 조건이나 루프가 Composable 함수 호출을 포함하는 경우, 이들도 재구성에 참여한다는 사실을 기억하는 것이 중요합니다.
@Composable
fun ProductList(
products: List<Product>,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier) {
items(products, key = { it.id }) { product ->
ProductItem(
product = product,
onAddToCart = { /* add to cart */ }
)
}
}
}
여러 Composable 함수를 사용하는 제품 검색 화면의 예를 고려해 봅시다. 여기에는 상태가 있는 입력 필드, 리스트 필터링, 빈 결과 처리 및 로딩과 같은 일반적인 패턴이 나타납니다.
data class Product(
val id: String,
val name: String,
val price: Double,
val category: String
)
@Composable
fun SearchScreen() {
var query by remember { mutableStateOf("") }
val products = remember(query) { getFilteredProducts(query) }
Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
OutlinedTextField(
value = query,
onValueChange = { query = it },
label = { Text("제품 검색") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
when (products) {
is Loading -> CircularProgressIndicator()
is Empty -> Text("결과를 찾을 수 없습니다")
is Result -> LazyColumn {
items(products.items, key = { it.id }) { product ->
ProductItem(product = product, onAddToCart = {})
}
}
}
}
}
이 예제는 다양한 이디옴을 동시에 보여줍니다: 검색 쿼리 상태를 유지하는 remember, 키로 필터링하는 remember(query), 세 가지 UI 상태를 위한 when, 그리고 효율적인 리스트 렌더링을 위한 LazyColumn. 이러한 각 이디옴은 Compose 애플리케션 개발의 실무 경험의 결과입니다.
Composable 함수는 일반 Kotlin 함수와 마찬가지로 파라미터를 허용하지만, 하나 중요한 차이점이 있습니다: 파라미터는 @Composable 어노테이션이 있는 lambda를 통해 전달되는 다른 Composable 함수가 일 수 있습니다. 이 메커니즘을 Slot API라고 하며, 재사용 가능한 컨테이너를 만드는 주요 패턴입니다.
Slot API는 전통적인 View 시스템에서 ViewGroup과 프로그래밍적인 하위 View 추가를 통해 해결된 문제를 해결합니다. addView 메서드 대신 Compose는 content lambda(@Composable () -> Unit 타입의 마지막 파라미터)를 사용합니다. 호출자는 이 lambda에 임의의 UI를 전달하고, 컨테이너는 그 레이아웃만 정의합니다.
Composable 함수의 파라미터는 기본값을 가질 수 있어 다양한 컨텍스트에서 사용이 간단해집니다. 권장되는 방법은 함수가 작업을 수행할 수 없는 파라미터만 필수로 만들고, 나머지는 합리적인 기본값을 제공하는 것입니다.
| 파라미터 | 타입 | 예제 |
|---|---|---|
| 필수 | 임의의 타입 | name: String |
| 선택 | 기본값 있음 | modifier: Modifier = Modifier |
| Content | @Composable () -> Unit | content: @Composable () -> Unit |
| Callback | @Composable 없는 Lambda | onClick: () -> Unit |
Compose 커뮤니티에서는 Composable 함수를 더 읽기 쉽고 예측 가능하게 만드는 다양한 구축된 이디옴이 나타났습니다. 첫 번째는 State Hoisting: 상태를 더 높은 레벨로 올리고, Composable 함수가 파라미터를 통해 이를 받습니다. 이를 통해 함수가 순수함수가 되고 다양한 컨텍스트에서 재사용될 수 있습니다.
둘째 이디옴은 Event-driven 파라미터입니다. Composable 함수에 ViewModel이나 useCase를 전달하는 대신, 특정 콜백(onSave, onDelete, onNavigateToDetail)만 전달됩니다. 이를 통해 결합도가 줄어들고 테스트가 간단해집니다.
셋째 이디옴은 CompositionLocal로, 컴포지션 트리를 통해 공유 데이터를 전달합니다. 테마, 화면 밀도, 현재 Route는 모두 CompositionLocal을 통해 전달되어 수십 개의 Composable 함수를 통한 파라미터 체인을 피합니다. 그러나 CompositionLocal을 과도하게 사용해서는 안 됩니다: 명시적인 파라미터가 언제나 보관적인 의존성보다 더 좋습니다.
// State Hoisting: 상태가 부모 함수로 올림
@Composable
fun CounterDisplay(
count: Int,
onIncrement: () -> Unit
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "카운터: $count", style = MaterialTheme.typography.headlineLarge)
Button(onClick = onIncrement) {
Text("+1")
}
}
}
// State Hoisting의 사용
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
CounterDisplay(
count = count,
onIncrement = { count++ }
)
}
자주 묻는 질문
네, return은 허용되지만 주의가 필요합니다. Compose는 개별 함수 레벨에서 재구성을 최적화하며, 이른 return이 이 최적화를 깨질 수 있습니다. 함수 본문 내에서 조건 연산자 if나 when을 사용하는 것이 더 좋습니다.
Kotlin에서 Unit은 빈 타입이 아닌 싱글톤 객체입니다. Composable 함수는 Unit을 반환하며, 이는 기술적으로 Unit 객체 자체를 반환함을 의미합니다. 그러나 실무에서는 이것이 중요하지 않습니다.
가변 컬렉션을 전달하는 것은 가능하지만 잘못된 습관입니다. 컬렉션이 변경되도 객체 참조가 동일하기 때문에 Compose가 이를 인식하지 못합니다. 추적되는 변경을 위해서는 불변 리스트나 mutableStateListOf를 사용하세요.
디버깅에는 Layout Inspector가 있는 Android Studio를 사용하세요. 현재 Composable 함수 트리, 파라미터 값 및 재구성 이유가 표시됩니다. 일반 Kotlin 디버거도 작동합니다.
Composable 함수는 험상 Unit을 반환하므로 반환 타입이 지정되지 않습니다. 다른 타입을 반환하려면 @Composable 어노테이션이 비 Unit 반환 타입과 호환되지 않기 때문에 컴파일 오류가 발생합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.