Foundation/Android
[Android] Jetpack Compose 개념
개발왕 금골드
2023. 1. 29. 13:01
반응형
Introduce
- Jetpack Compose는 Android를 위한 현대적인 선언형 UI 도구 키트이다.
- 더 적은 수의 코드, 강력한 도구, 직관적인 Kotlin API로 Android에서의 UI 개발을 간소화하고 가속화하여 앱에 생동감을 더해준다.
- 코드 감소
- 직관적
- 빠른 개발 과정
- 강력한 성능
간단한 Composable 함수
- Compose를 사용하면 데이터를 받아서 UI 요소를 내보내는 Composable function 집합을 정의하여 사용자 인터페이스를 빌드할 수 있다.
- @Composable 주석이 있어야 함수가 데이터를 UI로 변환하기 위한 함수라는 것을 Compose 컴파일러에게 알릴 수 있다.
- 함수는 데이터를 받는다. 매개변수가 있을 수 있으며 이를 통해 앱 로직이 UI를 형성할 수 있다.
- Composable 함수는 다른 Composable 함수를 호출하여 UI 계층 구조를 내보낸다.
- 함수는 아무것도 반환하지 않는다. UI를 내보내는 Compose 함수는 UI 위젯을 구성하는 대신 원하는 화면 상태를 설명하므로 아무것도 반환할 필요가 없다.
- 이 함수는 빠르고 멱등원(어떤 x값과 연산을 하여도 x가 나오는 함수)이며 부작용이 없다.
- 함수는 동일한 인수로 여러 번 호출될 때 동일한 방식으로 작동한다.
- 함수는 속성 또는 전역 변수 수정과 같은 부작용 없이 UI를 형성한다.
선언형 프로그래밍 패러다임
- 기존 Android UI를 업데이트하는 가장 일반적인 방법은 findViewById()와 같은 함수를 사용하여 트리를 검색하고 setText() 같은 메서드를 호출하여 노드를 변경하는 것이었다. 이러한 메서드는 View의 내부 상태를 변경한다.
- 기존 방법은 View를 수동으로 조작함으로써 오류가 발생할 가능성이 커지고 여러 위치에서 렌더링한다면 조작하는 중 실수로 일부 View의 업데이트를 잊기 쉽다. 또한 두 업데이트가 예상치 못한 방식으로 충돌할 우려가 있다.
- 선언형 UI 모델은 이러한 업데이트를 수동으로 처리하지 않고 View에 State를 부여하여 State의 변화에 따라 View가 업데이트되기 때문에 State만 신경 쓰면 된다.
- 화면 전체를 개념적으로 재생성한 후 필요한 변경사항만 적용하는 방식으로 작동된다.
선언형 패러다임 전환
- 명령형 객체 지향 UI 도구 키트를 사용하여 View Tree를 인스턴스화함으로써 UI를 초기화한다. 보통 XML 레이아웃 파일을 확장하여 이러한 작업이 이루어진다. 각 View 자체의 내부 상태를 유지하고 상호작용할 수 있도록 getter, setter 메서드를 노출한다. (findViewById())
- Compose의 선언형 접근 방식은 getter, setter 메서드를 노출하지 않는다. View를 객체로 노출하지 않는다.
- 동일한 Composable 함수를 다른 인수로 호출하여 UI를 업데이트한다. 이렇게 했을 때 ViewModel과 같은 아키텍처 패턴에 상태를 더 쉽게 제공할 수 있다.
- Composable은 식별 가능한 데이터가 업데이트될 때마다 현재 애플리케이션 상태를 UI로 변환한다.
- 앱 로직은 최상위의 Composable 함수에 데이터를 제공한다. 그러면 함수는 데이터를 사용하여 다른 Composable을 호출함으로써 UI를 형성하고 데이터를 해당 Composable 및 계층 구조 아래로 전달한다.
- 사용자가 UI와 상호작용할 때 UI는 onClick과 같은 이벤트를 발생시킨다. 이러한 이벤트를 앱 로직에 전달하여 앱의 상태를 변경한다.
- 상태가 변경되면 Composable 함수는 새 데이터와 함께 다시 호출되고 UI 요소가 다시 그려진다. 이를 재구성이라고 한다.
- 사용자가 UI 요소와 상호작용하여 이벤트가 트리거된다. 앱 로직이 이벤트에 응답하고 Composable 함수가 필요한 경우 새 매개변수를 사용하여 자동으로 다시 호출된다.
동적 콘텐츠
@Composable
fun Greeting(names: List<String>) {
for (name in names) {
Text("Hello $name")
}
}
- Composable 함수는 XML이 아닌 Kotlin으로 작성되기 때문에 다른 Kotlin 코드와 마찬가지로 동적일 수 있다.
- 기본 Kotlin 언어의 유연성을 완전히 활용할 수 있다. (if문, 루프, helper function 등)
재구성(Recomposition)
- 명령형 UI 모델에서 View를 변경하려면 View의 setter를 호출하여 내부 상태를 변경한다.
- Compose에서는 새 데이터를 사용하여 Composable 함수를 다시 호출한다.
- 이렇게 했을 때 함수가 재구성되며, 필요한 경우 함수에서 내보낸 View가 새로운 데이터로 다시 그려진다. Compose Framework는 변경된 구성요소만 지능적으로 재구성할 수 있다.
- 전체 UI 트리를 재구성하는 작업은 비용이 많이 든다. 재구성은 입력이 변경될 때 Composable 함수를 다시 호출하는 프로세스인데, 이는 함수의 입력이 변경될 때 발생한다. Compose는 새 입력을 기반으로 재구성할 때 변경되었을 수 있는 함수 또는 람다만 호출하고 나머지는 건너뛴다. 이렇게 함으로써 Compose의 재구성이 효율적으로 이루어질 수 있다.
Composable 함수는 순서와 관계없이 실행할 수 있다.
- Composable 함수가 반드시 표시된 순서대로 실행되는 것은 아니다.
- 각 함수는 독립적이어야 한다.
@Composable
fun ButtonRow() {
MyFancyNavigation {
StartScreen() // 여기서 어떤 전역 변수를 선언하고
MiddleScreen() // 여기서 해당 전역 변수에 트리거할 때 (부작용)
EndScreen() // 그 순서를 보장할 수 없기 때문에 좋지 않은 코드가 된다.
}
}
Composable 함수는 동시에 실행할 수 있다.
- Compose는 Composable 함수를 동시에 실행하여 재구성을 최적화할 수 있다. 이러한 최적화는 Composable 함수가 백그라운드 스레드 풀 내에서 실행될 수 있음을 의미한다.
- 애플리케이션이 올바르게 작동하려면 모든 Composable 함수에 부작용이 없어야 한다. (부작용을 실행해야 할 때는 콜백에서 부작용을 트리거해야 한다.)
- Composable 함수가 호출될 때 호출자와 다른 스레드에서 호출이 발생할 수 있다. 즉, Composable 람다의 변수를 수정하는 코드는 피해야 한다.
@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
var items = 0
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
items++ // Avoid! Side-effect of the column recomposing.
}
}
Text("Count: $items")
}
}
재구성은 가능한 한 많이 건너뛴다.
/**
* Display a list of names the user can click with a header
*/
@Composable
fun NamePicker(
header: String,
names: List<String>,
onNameClicked: (String) -> Unit
) {
Column {
// this will recompose when [header] changes, but not when [names] changes
Text(header, style = MaterialTheme.typography.h5)
Divider()
// LazyColumn is the Compose version of a RecyclerView.
// The lambda passed to items() is similar to a RecyclerView.ViewHolder.
LazyColumn {
items(names) { name ->
// When an item's [name] updates, the adapter for that item
// will recompose. This will not recompose when [header] changes
NamePickerItem(name, onNameClicked)
}
}
}
}
/**
* Display a single name the user can click.
*/
@Composable
private fun NamePickerItem(name: String, onClicked: (String) -> Unit) {
Text(name, Modifier.clickable(onClick = { onClicked(name) }))
}
- Compose는 header가 변경될 때 상위 요소 중 어느 것도 실행하지 않고 Column 람다로 건너뛸 수 있다. 그리고 Column을 실행할 때 Compose는 names가 변경되지 않았다면 LazyColumn의 항목을 건너뛸 수 있다.
- UI의 일부가 잘못된 경우 Compose는 업데이트해야 하는 부분문 재구성하기 위해 최선을 다한다. 모든 Composable 함수 및 람다는 자체적으로 재구성할 수 있다.
재구성은 낙관적이다.
- 즉, Compose는 매개변수가 다시 변경되기 전에 재구성을 완료할 것으로 예상한다.
- Compose가 Composable의 매개변수가 변경되었을 수 있다고 생각할 때마다 재구성이 시작된다. 재구성이 완료되기 전에 매개변수가 변경되면 Compose는 재구성을 취소하고 새 매개변수를 사용하여 재구성을 다시 시작할 수 있다.
- 재구성이 취소되면 Compose는 재구성에서 UI 트리를 삭제한다. 표시되는 UI에 종속되는 부작용이 있다면 구성이 취소된 경우에도 부작용이 적용된다. 이로 인해 일관되지 않은 앱 상태가 발생할 수 있다.
Composable 함수는 매우 자주 실행될 수 있다.
참고자료 :
Compose 이해 | Jetpack Compose | Android Developers
반응형