jetpack compose Basic
jetpack compose (Construct UI by describing what, not how)
- 네이티브 Android UI를 빌드하기 위한 최신 도구(선언형 UI)
- Jetpack Compose는 더 적은 수의 코드, 강력한 도구, 직관적인 Kotlin API로 Android에서의 UI 개발을 간소화하고 가속화
- composable 함수를 중심으로 빌드된다.
- composable 함수를 호출하여 원하는 요소를 정의하면 Compose 컴파일러가 나머지 작업을 완료.
legacy view system (how)과 비교 해보기
view system
1. .xml 레이아웃 파일에 필요한 view를 생성한다.
2. Ui controller(Activity/Fragment) 에서 view의 참조를 얻어온다.
3. view의 setter 함수를 이용해 뷰를 초기화 및 업데이트한다.
4. 변경된 view에 종속되어 같이 변경되는 view가 있다면, listener를 통해 같이 업데이트해준다.
- 하나의 view의 서로 다른 여러 view와 종속되어 변경이 된다면, 예상하지 못한 상황이 발생할 수 있다.
- 복잡한 형태의 ui거나 서로 종속된어 업데이트 되는 view의 수가 많다면, 매우 어렵다.
데이터의 상태의 변화를 UI에 동기화 되어 표현 한다는것, view 작업시 반복되는 일이다.
compose
1. composable 함수 작성(kotlin을 사용하여 1개의 언어로 간편하게 작성 가능)
2. 함수이기 때문에 전달 되는 params(상태)에 의해 제어 된다. (ui는 무엇-what을 보여주기만 할뿐 자체적인 상태를 가지지 않는다)
3. 상태가가 UI를 제어한다.
4. UI가 사용자와 상호작용으로 변경(Event) --> 상태 변경 --> 다른 상태에 종속된 UI 변경
1. Composable function
- UI의 구성 과정(요소 초기화, 상위 요소에 연결 등)에 집중하기 보다는 앱 모양을 설명하고 데이터 종속 항목을 제공하여 프로그래매틱 방식으로 앱의 UI를 정의할 수 있습니다.
- composable 함수를 만들려면 함수 위에 @Composable 주석을 추가하기만 하면 됩니다. (영문 대문자로 시작하는 이름)
- composable 함수는 다른 composable 함수 안 에서만 호출할 수 있다. (계층적 구조를 가지고 있다.)
- 매개변수로만 함수 전체가 통제되어야한다. 함수내부의 논리로 UI(Composable)에 영향을 미치면 안된다.
- side- effect가 없고 동일한 Args로 동일한 작동을 하는 composable 함수를 구현해야된다.
* composable 함수는 불변 하기에 참조해선 안된다.
* 전역변수, 또는 외부속성을 변경하면 안된다.
* side- effect를 발생시킬 가능성이 있는 지역변수를 함부로 사용하면 안된다.
2. Recomposition
함수의 내부 상태(State)의 변화 또는 다른 함수로 부터 매개변수의 변화로 인해 composable 함수를 재호출하여 UI를 다시 그리는것
- composable 함수는 코드상 작성된 순서와 관계 없이 실행될 수 있다 (execute in any order)
Compose에는 일부 UI 요소가 다른 UI 요소보다 우선순위가 높다는 것을 인식하고 그 요소를 먼저 그리는 옵션이 있다.
@Composable
fun ButtonRow() {
MyFancyNavigation {
StartScreen()
MiddleScreen()
EndScreen()
}
}
StartScreen() / MiddleScreen() / EndScreen() 중 어떤 Composable 함수가 먼저 실행이 되고 완료될지 모른다.
그렇기 때문에, StartScreen()이 일부 전역 변수(부작용)를 설정하고 MiddleScreen()에서 StartScreen()에서 설정한 전역변수를 활용하도록 할 수 없다. 각 함수는 독립적이어야 한다.
- composable 함수는 동시에 실행될 수 있다 (run in parallel)
composable 함수를 동시에 실행하여 재구성을 최적화할 수 있습니다. 이를 통해 Compose는 다중 코어를 활용하고 화면에 없는 구성 가능한 함수를 낮은 우선순위로 실행할 수 있습니다. Compose는 동시에 여러 스레드에서 이 함수를 호출하여 실행할 수있다.
애플리케이션이 올바르게 작동하도록 하려면 모든 composable 함수에 side- effect가 없어야 한다.
대신 UI Thread에서 항상 실행되는 onClick과 같은 콜백에서 side-effect를 트리거합니다.
즉, 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")
}
}
위 코드의 items는 서로 다른 Thread에서 composable 함수가 실행된다면, items 정확한 개수를 보장할 수 없다.
- Recomposition은 최대한 하지 않으려고 한다(Recomposition skips as much as possible)
가능하면, 필요한 부분만 Recomposition을 하려고 한다.
모든 composable 함수 및 람다는 자체적으로 혼자 재구성할 수 있습니다.
/**
* Display a list of names the user can click with a header
*/
@Composable
fun NamePicker(
header: String,
names: List<String>,
onNameClicked: (String) -> Unit
) {
Column {
// 매개변수 [header]가 변했을때 재구성된다,[names]의 변경에는 재구성 되지 않음
Text(header, style = MaterialTheme.typography.bodyLarge)
Divider()
// LazyColumn은 View System에서 RecyclerView와 유사하다
// items()는 RecyclerView.ViewHolder와 유사하다
LazyColumn {
items(names) { name ->
// [names]의 변경 items는 재구성됨
// [header]의 변경에는 재구성 되지 않음
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) }))
}
- Recompositon은 항상 최적화를 추구한다(Recomposition is optimistic)
상태가 변경되면 Recompostion 진행되는데, Recompostion 진행중에 다시 상태가 변경된다면, Compose는 재구성을 취소하고 새 매개변수를 사용하여 재구성을 다시 시작할 수 있습니다. 재구성이 취소되면 Compose는 재구성에서 UI 트리를 삭제합니다.
* Recompostion 매우 자주 일어날수 있다.
composable 함수는 UI 애니메이션의 모든 프레임에서 실행될 수 있습니다. 함수가 기기 저장소에서 읽기와 같이 비용이 많이
드는 작업을 실행하면 이 함수로 인해 UI 버벅거림이 발생할 수 있습니다.