안녕하세요. 저는 안드로이드 네이티브 앱 개발을 7년간 해오며 수많은 XML 레이아웃을 다뤄왔습니다. 익숙한 ConstraintLayout, 복잡한 RecyclerView Adapter, Fragment 간의 연결 등 이제는 손에 익었지만, Jetpack Compose의 등장은 그런 제 개발 습관에 큰 충격을 줬습니다.
이번 글에서는 XML 기반 개발에 익숙한 입장에서 Compose로 전환하며 겪었던 시행착오와 느낀 점을 공유하려 합니다.
가장 먼저 시도한 건, 기존에 XML로 구성된 간단한 로그인 화면을 Compose로 옮겨보는 것이었습니다.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp">
<EditText
android:id="@+id/etEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="이메일 입력" />
<EditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="비밀번호 입력"
android:inputType="textPassword" />
<Button
android:id="@+id/btnLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="로그인" />
</LinearLayout>
@Composable
fun LoginScreen() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center
) {
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("이메일 입력") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("비밀번호 입력") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { /* 로그인 로직 */ },
modifier = Modifier.fillMaxWidth()
) {
Text("로그인")
}
}
}
이처럼 XML을 Compose로 변환하는 과정은 처음엔 낯설지만, 상태 기반 UI 개념과 Modifier, Column, TextField, Button 등 핵심 컴포저블을 익히면서 빠르게 적응할 수 있었습니다.
XML 기반 UI는 선언은 구조적으로 명확하지만, 실제 동작과 상호작용까지 구현하려면 다음과 같은 어려움이 있었죠:
그럼에도 “눈에 보이는 UI와 코드의 분리”가 직관적이었기에 오랫동안 써왔습니다.
Compose의 첫 인상은 다음과 같았습니다:
@Composable이 도대체 뭐지?remember, mutableStateOf 같은 상태 변수?게다가 IDE 환경도 아직 완벽하지 않아서, 처음엔 막막했습니다. “이거 그냥 Flutter 아니야?”라는 생각도 들었습니다.
Compose는 선언형 UI입니다. 즉, "어떻게 그릴지"가 아니라 **"어떤 상태에서 어떤 모습이어야 하는지"**를 정의합니다.
가장 인상 깊었던 점은:
LazyColumn)Modifier를 통해 UI 속성을 유연하게 정의예전에는 버튼 클릭 시 텍스트뷰를 숨기려면 textView.visibility = View.GONE 같은 imperative 코드를 작성했지만, 이제는 if (isVisible) { Text(...) }처럼 상태가 UI를 주도합니다.
var count by remember { mutableStateOf(0) }
state hoisting 개념을 몰랐을 땐, 상태가 Compose 함수 내에 갇혀버리기도 했습니다.
컴포저블이 재구성되면서 예상치 못한 동작이 발생하기도 했습니다. 특히, lambda 함수나 remember 설정이 잘못되면 무한 recomposition이...
프리뷰가 항상 정확하지 않다는 점을 간과하고, 실제 디바이스에선 레이아웃 깨짐을 여러 번 경험했습니다.
처음엔 낯설었지만, 이제는 새로운 프로젝트를 시작할 땐 XML보다 Compose를 먼저 떠올립니다. 이유는:
물론 XML이 필요한 레거시 프로젝트나 복잡한 뷰 특수 케이스는 여전히 존재합니다. 하지만 앞으로의 안드로이드 개발에서 Compose는 중심에 설 것입니다.
이 글은 선언형 UI에 대한 첫 걸음을 기록한 것이며, 다음 글에서는 Compose + Hilt + MVVM 구조를 실제 코드와 함께 다뤄보겠습니다.
👉 혹시 당신은 Compose를 처음 접했을 때 어땠나요? 댓글로 공유해주세요.
감사합니다!