안드로이드 개발을 시작하면서 UI를 구성하는 방식에는 명령형 UI와 선언형 UI라는 두 가지 접근 방식이 있습니다. 이번 글에서는 이 두 가지 UI 방식의 차이점과 Jetpack Compose에 대해 알아보겠습니다.
UI는 사용자가 시스템과 상호작용하는 중요한 요소입니다. 안드로이드에서 UI를 구성하는 방법은 크게 명령형 UI와 선언형 UI로 나눌 수 있습니다.
명령형 UI는 개발자가 직접 명령을 통해 화면을 제어하는 방식입니다. 예를 들어 XML을 사용해 뷰를 정의하고, setText()
와 같은 메서드를 통해 화면 요소를 갱신합니다. 이 방식은 세밀한 제어가 가능하지만, 코드가 복잡해지고 상태 관리가 어렵다는 단점이 있습니다.
명령형 UI에서는 UI의 각 요소를 명령을 통해 수동으로 업데이트하기 때문에, 여러 요소의 상태를 유지하고 일관성을 관리하는 것이 까다로울 수 있습니다. 이러한 방식은 변화가 많은 UI나 복잡한 상호작용을 구현할 때 코드가 길어지고 유지보수가 어려워질 수 있습니다.
선언형 UI는 화면의 최종 상태를 선언하는 방식입니다. 데이터가 변경되면 시스템이 UI를 자동으로 업데이트합니다. Jetpack Compose가 대표적인 선언형 UI 도구입니다. Compose에서는 @Composable
함수로 UI를 정의하고, 데이터가 변경될 때마다 Compose가 자동으로 화면을 갱신해줍니다. 이는 코드 가독성을 높이고 유지보수를 쉽게 해줍니다.
선언형 UI에서는 UI를 어떻게 업데이트할지 신경 쓸 필요 없이, 데이터의 상태만 관리하면 됩니다. 상태가 변경되면 Compose가 이를 감지하고 필요한 부분만 재구성해서 효율적으로 UI를 업데이트합니다. 이로 인해 코드의 복잡도가 낮아지고, 변화가 많은 UI에서도 안정적인 구현이 가능합니다.
명령형 UI: 개발자가 직접 UI 상태를 관리하며, 코드의 세밀한 제어가 가능하지만 복잡도가 높습니다.
선언형 UI: 최종 상태만을 선언하고, 시스템이 상태 변화에 따른 UI 업데이트를 처리해 가독성과 유지보수가 용이합니다.
Jetpack Compose는 2021년에 출시된 안드로이드 개발을 위한 선언형 UI 도구입니다. Kotlin을 기반으로 해서 간결하고 직관적으로 UI를 구성할 수 있고, 상태 변화에 따른 UI 업데이트를 자동으로 처리해주는 반응형 UI를 제공합니다. 또한 기존의 XML 뷰와 상호작용할 수 있어서 유연하게 사용할 수 있습니다.
선언형 UI: UI 상태와 구조를 선언적으로 표현해서 코드의 가독성을 높입니다.
Kotlin 기반: 코루틴과 확장 함수 등 Kotlin의 다양한 기능을 활용할 수 있습니다.
재사용 가능한 컴포넌트: 작은 단위의 컴포넌트를 통해 복잡한 UI를 쉽게 구성할 수 있습니다.
반응형 UI: 상태 변경에 따라 UI가 자동으로 반응하며 업데이트됩니다.
기존 앱과의 호환성: XML과 Compose를 함께 사용할 수 있어서, 점진적인 마이그레이션이 가능합니다.
Compose를 사용하면 기존 명령형 UI에 비해 코드의 양을 줄이고, 유지보수를 용이하게 만들 수 있습니다. 특히, Kotlin의 장점을 적극 활용해서 간결하고 직관적인 UI 구성이 가능합니다.
Compose의 기본 컴포넌트로는 Text
, Button
, TextField
등이 있고, Column
, Row
, Box
같은 레이아웃 컴포저블을 통해 UI 요소를 배치할 수 있습니다. 각 컴포넌트는 Modifier
를 사용해 크기, 색상, 패딩 등을 조정할 수 있고, @Preview
어노테이션을 통해 에뮬레이터 없이도 미리보기를 할 수 있습니다.
Modifier
는 컴포저블에 스타일이나 동작을 부여하는 데 사용됩니다. 예를 들어 Modifier.padding(16.dp)
를 사용해 컴포넌트에 여백을 줄 수 있습니다. Modifier의 순서는 최종 결과에 영향을 미치기 때문에 순서에 주의해야 합니다.
Modifier
를 사용하면 컴포넌트의 배치, 크기, 여백, 클릭 동작 등을 손쉽게 정의할 수 있습니다. 여러 Modifier를 체인으로 연결해 사용하고, 순서에 따라 적용 결과가 달라질 수 있습니다. 예를 들어 padding
을 먼저 적용할지, clickable
을 먼저 적용할지에 따라 사용자 경험이 달라질 수 있습니다.
Compose UI는 안드로이드의 Activity와 함께 동작하며, Activity의 생명주기를 고려해서 안정적인 UI를 제공해야 합니다. Activity의 주요 생명주기 콜백은 onCreate()
, onStart()
, onResume()
, onPause()
, onStop()
, onDestroy()
가 있습니다. 이 콜백들을 적절히 활용해서 메모리를 해제하거나, 데이터 업데이트를 관리하는 것이 중요합니다.
예를 들어, onCreate()
에서는 UI 초기화를, onPause()
나 onStop()
에서는 리소스 해제를, onResume()
에서는 데이터를 다시 불러오는 작업을 할 수 있습니다. Compose와 Activity 생명주기를 잘 연동하면, 사용자가 앱을 나갔다가 돌아오더라도 자연스럽고 끊김 없는 UI를 제공할 수 있습니다.
Compose는 상태 기반의 UI이기 때문에 Activity의 생명주기와 상태 관리를 함께 고려해야 합니다. 이를 위해 remember나 rememberSaveable 같은 상태 관리 도구를 활용해서 Activity가 일시 중지되었다가 다시 시작될 때도 UI 상태를 유지할 수 있습니다. 이러한 도구들은 Compose의 강력한 상태 관리 기능을 뒷받침해서 사용자가 앱을 사용하는 동안 일관된 경험을 제공합니다.
Compose에서의 상태 관리는 UI의 일관성을 유지하는 데 중요합니다. remember
는 컴포저블이 재구성될 때 상태를 기억하게 해주고, rememberSaveable
은 상태를 장기적으로 저장해서 Activity나 화면 회전 등으로 컴포저블이 재생성될 때도 값을 유지합니다. 이런 도구들을 통해 사용자는 화면 전환이나 기기 회전 시에도 끊김 없는 경험을 할 수 있습니다.
이제 Compose를 활용해 간단한 로그인 화면을 구현해보겠습니다. TextField
를 사용해 사용자 입력을 받고, Button
을 통해 로그인을 시도하는 형태로 구성할 수 있습니다. Modifier를 활용해 패딩과 정렬을 설정하고, @Preview
를 통해 UI를 미리 확인하면서 개발할 수 있습니다.
TextField: 사용자 이름과 비밀번호를 입력받는 입력 필드를 생성합니다.
Button: 입력된 정보를 기반으로 로그인 동작을 수행하는 버튼을 만듭니다.
Modifier: 각 컴포넌트에 패딩과 여백을 추가해서 깔끔한 UI를 구성합니다.
@Composable
fun LoginScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
TextField(
value = username,
onValueChange = { username = it },
label = { Text("아이디를 입력해주세요") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
TextField(
value = password,
onValueChange = { password = it },
label = { Text("비밀번호를 입력해주세요") },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { /* 로그인 로직 */ },
modifier = Modifier.fillMaxWidth()
) {
Text("로그인")
}
}
}
위의 예제처럼 Compose를 사용하면 간결한 코드로 UI를 구성할 수 있고, 선언형 접근 방식을 통해 데이터 상태에 따라 화면이 자동으로 갱신됩니다.
Jetpack Compose는 매우 강력한 도구지만 몇 가지 한계점도 있습니다. 대표적으로 선언형 UI의 특성상 모든 상태를 데이터로 관리해야 하기 때문에 상태 관리가 복잡해질 수 있습니다. 특히 대규모 애플리케이션에서는 상태 관리의 구조를 잘 설계하지 않으면 코드의 유지보수가 어려워질 수 있습니다.
또한, 기존 명령형 UI에서 Compose로 전환하는 과정에서 XML 기반의 레이아웃과 Compose의 레이아웃을 혼합해 사용하는 경우 호환성 문제나 복잡한 통합 작업이 발생할 수 있습니다. 이러한 문제를 해결하려면 Compose의 도입을 점진적으로 진행하고, 필요한 경우 기존의 View와 Compose를 혼합해서 사용하는 게 좋습니다.
이번 글에서는 명령형 UI와 선언형 UI의 차이점, 그리고 Jetpack Compose의 주요 개념과 기본적인 사용법에 대해 알아봤습니다. Compose를 활용하면 더 간결하고 유지보수하기 쉬운 코드를 작성할 수 있고, 상태 변화에 따라 자동으로 UI를 갱신할 수 있어 현대적인 안드로이드 개발에 매우 유용해요. 사실 당연히 컴포즈 처럼 짜는줄..
남은 세미나를 통해서 어엿한 안드로이드스러운 개발자가 될때까지 화이팅!