XML에서 Compose로: 7년 차 개발자의 선언형 UI 적응기

파도타는 개발자·2025년 6월 19일

adaptation

목록 보기
2/4

안녕하세요. 저는 안드로이드 네이티브 앱 개발을 7년간 해오며 수많은 XML 레이아웃을 다뤄왔습니다. 익숙한 ConstraintLayout, 복잡한 RecyclerView Adapter, Fragment 간의 연결 등 이제는 손에 익었지만, Jetpack Compose의 등장은 그런 제 개발 습관에 큰 충격을 줬습니다.

이번 글에서는 XML 기반 개발에 익숙한 입장에서 Compose로 전환하며 겪었던 시행착오와 느낀 점을 공유하려 합니다.


🌱 Compose로 첫 발을 내딛다: 기존 XML 화면 이식기

가장 먼저 시도한 건, 기존에 XML로 구성된 간단한 로그인 화면을 Compose로 옮겨보는 것이었습니다.

✅ 기존 XML 레이아웃 예시

<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>

🛠 Jetpack Compose로 변환한 코드

@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의 익숙함, 그리고 한계

XML 기반 UI는 선언은 구조적으로 명확하지만, 실제 동작과 상호작용까지 구현하려면 다음과 같은 어려움이 있었죠:

  • 복잡한 뷰 계층 구조
  • findViewById / ViewBinding 남용
  • 동적 뷰 생성 시 불편함
  • RecyclerView의 ViewHolder 패턴 반복
  • ConstraintLayout 제약조건 관리의 피로감

그럼에도 “눈에 보이는 UI와 코드의 분리”가 직관적이었기에 오랫동안 써왔습니다.


🚀 Compose의 첫 인상: 선언형의 낯섦과 자유로움

Compose의 첫 인상은 다음과 같았습니다:

  • XML이 없다?! 뷰는 다 코드로 만든다?
  • @Composable이 도대체 뭐지?
  • remember, mutableStateOf 같은 상태 변수?
  • 레이아웃 프리뷰는 왜 가끔씩 안 뜨지?

게다가 IDE 환경도 아직 완벽하지 않아서, 처음엔 막막했습니다. “이거 그냥 Flutter 아니야?”라는 생각도 들었습니다.


🧱 구조의 전환: 선언형 UI 사고로의 전환

Compose는 선언형 UI입니다. 즉, "어떻게 그릴지"가 아니라 **"어떤 상태에서 어떤 모습이어야 하는지"**를 정의합니다.

가장 인상 깊었던 점은:

  • UI 상태 = 데이터 상태
  • XML + Adapter + ViewHolder 없이 리스트 구현 (예: LazyColumn)
  • Modifier를 통해 UI 속성을 유연하게 정의

예전에는 버튼 클릭 시 텍스트뷰를 숨기려면 textView.visibility = View.GONE 같은 imperative 코드를 작성했지만, 이제는 if (isVisible) { Text(...) }처럼 상태가 UI를 주도합니다.


💥 시행착오 Best 3

1. 상태가 꼬임

var count by remember { mutableStateOf(0) }

state hoisting 개념을 몰랐을 땐, 상태가 Compose 함수 내에 갇혀버리기도 했습니다.

2. recomposition 지옥

컴포저블이 재구성되면서 예상치 못한 동작이 발생하기도 했습니다. 특히, lambda 함수나 remember 설정이 잘못되면 무한 recomposition이...

3. 프리뷰에 너무 의존

프리뷰가 항상 정확하지 않다는 점을 간과하고, 실제 디바이스에선 레이아웃 깨짐을 여러 번 경험했습니다.


🌿 지금은?

처음엔 낯설었지만, 이제는 새로운 프로젝트를 시작할 땐 XML보다 Compose를 먼저 떠올립니다. 이유는:

  • 더 적은 코드로 더 많은 기능 구현 가능
  • 커스텀 뷰 구현이 훨씬 간편
  • MVVM, Hilt 등과의 궁합도 좋음

물론 XML이 필요한 레거시 프로젝트나 복잡한 뷰 특수 케이스는 여전히 존재합니다. 하지만 앞으로의 안드로이드 개발에서 Compose는 중심에 설 것입니다.


✍️ 마치며

이 글은 선언형 UI에 대한 첫 걸음을 기록한 것이며, 다음 글에서는 Compose + Hilt + MVVM 구조를 실제 코드와 함께 다뤄보겠습니다.

👉 혹시 당신은 Compose를 처음 접했을 때 어땠나요? 댓글로 공유해주세요.

감사합니다!

profile
Im deep diving in Android

0개의 댓글