1. Jetpack Compose란?
Jetpack Compose는 Android의 최신 UI 툴킷으로, 선언형(Declarative) 방식을 기반으로 UI를 구성할 수 있도록 설계되었다. 기존의 XML 기반 UI 작성 방식보다 직관적이며, 코드 양을 줄이고, 재사용성을 높일 수 있는 장점이 있다.
Compose는 Kotlin 언어를 기반으로 동작하며, 보다 유연한 UI 개발이 가능하다. 특히 상태(State) 기반의 UI 렌더링 방식을 지원하여 동적인 UI를 효율적으로 관리할 수 있다.
2. Jetpack Compose의 주요 특징
선언형 UI (Declarative UI)
기존 XML 방식은 명령형(Imperative) 방식으로, UI 요소를 직접 조작하고 업데이트해야 했다. 하지만 Compose는 선언형 방식을 채택하여 상태(State)에 따라 UI가 자동으로 갱신된다. 예를 들어, findViewById()
를 사용하지 않고도 UI를 쉽게 업데이트할 수 있다.
@Composable
fun CounterApp() {
var count by remember { mutableStateOf(0) }
Column {
Text(text = "Count: $count", fontSize = 20.sp)
Button(onClick = { count++ }) {
Text("Increase")
}
}
}
위 코드에서 count
값이 변경될 때마다 UI가 자동으로 다시 렌더링된다.
더 적은 코드 (Less Boilerplate)
- XML과
findViewById()
,ViewBinding
없이 UI를 직접 Kotlin 코드로 작성할 수 있다. RecyclerView
및Adapter
없이 리스트 UI를 간단하게 구현할 수 있다.
재사용성 및 확장성
- UI 컴포넌트를 Composable 함수로 만들어 쉽게 재사용할 수 있다.
- XML 기반에서는
<include>
태그를 활용해 재사용했지만, Compose에서는 Composable 함수를 이용하면 더욱 직관적이고 효율적이다.
@Composable
fun CustomButton(text: String, onClick: () -> Unit) {
Button(onClick = onClick) {
Text(text)
}
}
이제 CustomButton("Click Me") { /* 이벤트 핸들링 */ }
와 같이 쉽게 사용할 수 있다.
애니메이션 및 UI 업데이트가 쉬움
Modifier
와animate*()
API를 활용해 자연스러운 애니메이션을 쉽게 추가할 수 있다.
@Composable
fun AnimatedBox() {
var isVisible by remember { mutableStateOf(true) }
val alpha by animateFloatAsState(targetValue = if (isVisible) 1f else 0f)
Column {
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Red)
.alpha(alpha)
)
Button(onClick = { isVisible = !isVisible }) {
Text("Toggle Visibility")
}
}
}
Kotlin과의 강력한 연계
Compose는 Kotlin 언어와 100% 호환되며, 코루틴(Coroutines) 및 Flow와 같은 최신 Kotlin 기능과도 쉽게 결합할 수 있다. 따라서 비동기 작업 처리가 훨씬 간편해진다.
@Composable
fun LoadDataScreen() {
val scope = rememberCoroutineScope()
var text by remember { mutableStateOf("Loading...") }
LaunchedEffect(Unit) {
scope.launch {
delay(2000)
text = "Data Loaded"
}
}
Text(text)
}
2. Jetpack Compose 핵심 개념 Deep Dive
Jetpack Compose의 핵심 개념을 깊이 있게 살펴보자.
Composable 함수
Jetpack Compose의 UI는 Composable 함수로 구성된다. 이는 @Composable
애너테이션을 붙여 선언하며, XML 레이아웃을 대체하는 역할을 한다.
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
Composable 함수의 특징
- 함수형 선언 방식
- UI는 상태(state)에 따라 변화하며, 이를 함수형 방식으로 선언한다.
- 호출 순서 무관
- 기존 View 시스템에서는 XML을 정의한 순서대로 UI가 그려지지만, Composable 함수는 호출 순서와 상관없이 독립적으로 동작한다.
- 단순한 함수 호출
- XML처럼 별도의 뷰 바인딩 없이, 함수를 호출하는 것만으로 UI를 구성할 수 있다.
Recomposition이란?
- Recomposition(재구성)은 UI가 변경된 상태에 따라 다시 그려지는 과정을 의미한다.
- 특정 State가 변경될 때 그와 관련된 UI만 부분적으로 갱신된다.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increase")
}
}
}
count
값이 변할 때마다 Text와 Button이 포함된 Column만 다시 그려진다.- 불필요한 전체 UI 리렌더링을 방지하여 성능 최적화에 유리하다.
- 더 자세한건 아래 링크에 정리하였다.
https://superohinsung.tistory.com/380
[Android] Jetpack Compose의 Recomposition 정리
1. Recomposition(리컴포지션)이란?Recomposition(리컴포지션)은 Jetpack Compose에서 상태(State)의 변경에 따라 필요한 UI만 다시 그리는 과정을 의미한다.기존 XML 기반 View 시스템에서는 findViewById()를 통해 특
superohinsung.tistory.com
상태 유지와 remember
remember
를 사용하면 Composable 함수가 재구성되더라도 기존 상태를 유지할 수 있다.
var text by remember { mutableStateOf("Hello!") }
remember
를 사용하지 않으면, Recomposition이 발생할 때마다 초기 상태로 돌아감.
rememberSaveable
vs remember
remember |
rememberSaveable |
|
---|---|---|
상태 유지 범위 | 동일한 Composable 내에서만 유지 | 프로세스 종료 후에도 유지 |
데이터 저장 방식 | 메모리에 저장 | Bundle을 활용해 저장 |
사용 예시 | 간단한 UI 상태 | 화면 회전 시에도 유지할 상태 |
var name by rememberSaveable { mutableStateOf("User") }
rememberSaveable
은 화면 회전이나 프로세스 재시작 후에도 데이터를 유지한다.
https://velog.io/@thisyoon97/Remember-Vs.-rememberSaveable
Remember Vs. rememberSaveable
Jetpack Compose는 안드로이드 앱 개발을 혁신적으로 변화시켰으며, UI 컴포넌트를 더 간편하게 구축하고 관리할 수 있게 해준다. Jetpack Compose에서 상태를 관리하는 데 자주 사용되는 두 가지 함수는
velog.io
State Hoisting (상태 끌어올리기)
- Composable 함수에서 상태(state)를 직접 관리하는 대신, 상위 함수에서 관리하도록 위임하는 패턴.
- 단일 방향 데이터 흐름 (Unidirectional Data Flow, UDF)을 보장한다.
왜 State Hoisting을 사용할까?
- 재사용성 증가 → Composable 함수를 여러 곳에서 쉽게 재사용 가능.
- 테스트 용이 → UI 로직과 상태 관리가 분리되어 테스트하기 쉬움.
- 코드 유지보수성 증가 → 상태 관리가 중앙 집중화되어 예측 가능.
State Hoisting 적용 예시
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
Column {
Text("Count: $count")
Button(onClick = onIncrement) {
Text("Increase")
}
}
}
@Composable
fun CounterScreen() {
var count by remember { mutableStateOf(0) }
Counter(count = count, onIncrement = { count++ })
}
- CounterScreen에서 count를 관리하고, Counter는 단순히 UI를 그리는 역할만 한다.
- 이렇게 하면 Counter는 독립적인 UI 컴포넌트로 재사용 가능.
Modifier를 활용한 스타일링
- Composable 함수에 다양한 스타일을 적용하는 객체.
- Padding, Background, 클릭 이벤트, Animation 등 UI 속성을 조작할 수 있다.
Text(
text = "Hello, Compose!",
modifier = Modifier
.padding(16.dp)
.background(Color.Blue)
.fillMaxWidth()
.clickable { /* 클릭 이벤트 */ }
)
Modifier의 특징
- 순서가 중요하다!
- padding → background 순서와 background → padding 순서는 결과가 다름.
- Modifier를 적용하는 순서에 따라 UI가 다르게 보일 수 있다.
- Composable 함수는 Modifier를 기본 매개변수로 받아야 한다
- 이렇게 하면 UI 재사용성이 증가.
Side Effects와 LaunchedEffect
, remember
- Composable 함수는 순수해야 한다.
- 즉, Composable이 실행될 때 외부 상태(예: 네트워크 요청, SharedPreferences, DB 접근 등)에 영향을 주는 작업을 수행하면 안 됨.
- 하지만, UI가 처음 렌더링될 때 네트워크 요청을 해야 하는 경우도 있음 → 이를 위해 Side Effect API 사용.
LaunchedEffect
Composable이 최초 실행될 때 한 번 실행되는 코드를 정의할 때 사용.
@Composable
fun FetchData() {
var text by remember { mutableStateOf("Loading...") }
LaunchedEffect(Unit) {
delay(2000)
text = "Data Loaded"
}
Text(text = text)
}
- Unit을 Key로 전달하면, Composable이 처음 실행될 때만 코드가 실행됨.
- 네트워크 요청이나 데이터 로딩 같은 작업에 유용.
rememberCoroutineScope
Composable 외부에서도 사용할 수 있는 Coroutine Scope를 제공.
@Composable
fun ButtonWithCoroutine() {
val scope = rememberCoroutineScope()
var text by remember { mutableStateOf("Click Me") }
Button(onClick = {
scope.launch {
delay(1000)
text = "Clicked!"
}
}) {
Text(text)
}
}
정리
개념 | 핵심 내용 |
---|---|
Composable 함수 | Jetpack Compose의 UI를 구성하는 기본 단위 |
Recomposition & 상태 관리 | UI를 상태(state) 기반으로 갱신, remember 와 rememberSaveable 활용 |
State Hoisting | 상태를 상위 Composable에서 관리하여 재사용성과 유지보수성을 높임 |
Modifier | UI 스타일 및 이벤트를 설정하는 체이닝 방식의 속성 객체 |
Side Effects | LaunchedEffect , rememberCoroutineScope 등을 사용하여 비동기 작업 수행 |
Jetpack Compose의 핵심 개념을 이해하면 보다 효율적인 Android UI 개발이 가능하다.
4. XML과 Jetpack Compose 비교 표
항목 | XML 기반 (View System) | Jetpack Compose |
---|---|---|
UI 구성 방식 | 명령형 (Imperative) | 선언형 (Declarative) |
코드 스타일 | XML + Kotlin/Java 분리 | Kotlin 코드 내에서 UI 작성 |
View 참조 | findViewById() , View Binding 필요 |
Composable 함수로 직접 사용 |
상태 관리 | LiveData , ViewModel , Data Binding 사용 |
remember , mutableStateOf() 활용 |
UI 변경 방식 | setText() , setVisibility() 등 수동 업데이트 |
상태 변경 시 자동 UI 업데이트 |
RecyclerView | Adapter + ViewHolder 필요 | LazyColumn , LazyRow 로 간편 구현 |
애니메이션 | ObjectAnimator , ViewPropertyAnimator 필요 |
animate*() API 사용 |
다크 모드 대응 | night 테마 XML 필요 |
isSystemInDarkTheme() 활용 |
5. XML vs Compose UI 코드 비교
기본 UI 생성 (TextView 예제)
XML 방식
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="20sp"/>
val textView: TextView = findViewById(R.id.textView)
textView.text = "Updated Text"
Jetpack Compose 방식
@Composable
fun Greeting() {
Text(text = "Hello World!", fontSize = 20.sp)
}
RecyclerView vs LazyColumn
XML + RecyclerView 방식
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
class MyAdapter(private val items: List<String>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = items[position]
}
override fun getItemCount() = items.size
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.textView)
}
}
recyclerView.adapter = MyAdapter(listOf("Item 1", "Item 2", "Item 3"))
Jetpack Compose 방식 (LazyColumn)
@Composable
fun ItemList(items: List<String>) {
LazyColumn {
items(items) { item ->
Text(text = item, fontSize = 20.sp, modifier = Modifier.padding(8.dp))
}
}
}
6. ComposeUI와 XML의 UI 렌더링 방식 비교
XML 기반 View 시스템
- XML을 기반으로 View를 정의하고,
findViewById()
또는 ViewBinding을 통해 객체화한 후 UI를 조작한다. - 뷰 계층 구조가 트리(Tree) 형태로 구성되며, UI 변경 시 특정 View를 찾아 업데이트해야 한다.
- View는 명령형 방식(Imperative)으로 상태를 직접 변경해야 한다.
- UI 업데이트 시 invalidate() 또는 requestLayout() 호출이 필요할 수 있다.
Jetpack Compose UI 렌더링 방식
Composable
함수가 상태(State)에 따라 UI를 렌더링한다.- 변경된 부분만 자동으로 다시 그려지는 Recomposition 개념이 적용된다.
- UI 변경 시 전체 View를 다시 그리는 것이 아니라, 상태가 변경된 부분만 렌더링되므로 성능이 최적화된다.
- UI와 로직이 하나의 함수 내에서 관리될 수 있어 분리와 재사용성이 뛰어남
@Composable
fun Greeting(name: String) {
Text("Hello, $name")
}
이 함수가 호출될 때마다 새로운 Text()
가 그려지는 것이 아니라, name
값이 변경될 때만 해당 UI가 다시 렌더링된다.
7. 성능 최적화 및 유지보수 고려 사항
성능 차이 분석 (UI 렌더링 방식 비교)
- 기존 View 시스템은 XML 기반으로 레이아웃을 설정하고, Java/Kotlin 코드에서 이를 참조해 변경해야 하지만, Jetpack Compose는 선언형 UI 방식으로 직접 UI 상태를 변경 가능.
- Recomposition을 통해 변경된 부분만 다시 그려 최적화 효과를 가짐.
유지보수 관점에서의 차이
- 코드 가독성: XML과 Kotlin을 혼합하던 기존 방식보다 코드가 일관되어 가독성이 높음.
- 재사용성: Composable 함수 단위로 UI를 구성하여 여러 화면에서 재사용이 용이함.
- 테스트 용이성: UI 테스트가 기존 Fragment 기반보다 간단하며, UI 단위 테스트 작성이 쉬움.
메모리 및 렌더링 최적화 방법
- remember와 rememberSaveable을 적절히 활용하여 불필요한 Recomposition 방지.
- LazyColumn을 사용해 효율적인 리스트 렌더링.
- LaunchedEffect와 rememberCoroutineScope로 불필요한 코루틴 실행 방지.
'Android > Study' 카테고리의 다른 글
[Android] 인텐트(Intent) 및 인텐트 필터(Intent Filter) (0) | 2025.02.20 |
---|---|
[Android] Jetpack Compose의 Recomposition 정리 (0) | 2025.02.20 |
[Android] SearchView 사용기 (0) | 2024.06.24 |
[Android] WebView 사용기 (0) | 2024.06.24 |
[Android] WebSocket를 통해서 채팅 기능 구현 feat. Clean Architecture, Hilt (0) | 2024.06.24 |