개요
주변 프로젝트 하는 사람들을 보면 컴포즈를 많이 적용하고 있다. 도대체 컴포즈가 뭐길래? 선언형 UI? 기존 xml로 작업하는게 더 쉽고, 편하지 않아? 라는 생각에 지금까지 컴포즈 공부를 안하고 있었던 것 같다.
하지만, 처음 해본 백엔드도 재밌던 입장에서 컴포즈도 재밌을 것이고, 주변 사람들이 추천한 만큼 편할 것이라고 생각이 바뀌게 되었다. 그래서 컴포즈 공식문서를 정리해 보며, 컴포즈에 대한 지식을 채워나갈 것이다.
오늘은 컴포즈에 대한 이해 부분을 정리해보겠다.
Compose
xml로 뷰를 그리는것은 계층적으로 이루어져있다. ConstraintLayout 안에 TextView나 ImageView가 들어가 있듯이, ViewGroup 안에 View가 위치해 있다. 이것은 굉장히 복잡한 UI에서는 여러가지 View가 중첩되어 있어, 유지 보수하기 힘들고 건드리기가 무섭다.
또한, 사용자가 수행하는 이벤트에 따라 직접 데이터를 갱신해줘야 한다. 이때 오류들이 발생하기도 했다( 다른 뷰에 데이터를 넣는다는지?!)
하지만 컴포즈의 경우 전체 뷰를 생성한 후에 변경 사항에 대해 적용하는 방식으로 동작한다고 한다.
-> 즉, 직접 데이터가 변경될 때마다 수동적으로 뷰에 데이터를 갱신해주는 것(xml) VS 변경 된 데이터의 UI를 자동으로 다시 그려주는 것(Compose).
컴포즈는 선언형 UI 도구 키트이다.
Simple Composable Function
위의 코드와 같이 함수위에 @Composable 어노테이션을 지정했다. 이를 컴포저블 함수라고 한다.
이 어노테이션을 바탕으로 컴포즈 컴파일러에게 이 함수가 주어진 데이터를 UI로 변환하는 것을 알려준다.
위의 코드에서는 Text() 라는 컴포저블 함수를 호출해 옆에 그림처럼 "Hello Text"라는 텍스트를 UI에 표시한다.
이때, 컴포저블 함수는 어떠한 리턴값도 없는데 이는 UI를 그려주기 때문에 어떠한 것도 리턴할 필요가 없기 때문이다.
또한, 역시 함수형 프로그래밍 답게 동일한 매개변수로 여러 번 호출되더라도, 동일한 UI를 그려내며 전역 변수와 같은 다른 값을 사용하지 않는다.
선언형 패러다임으로의 이동
기존에 사용했던 xml과 같이 객체 지향을 지원하는 명령형 UI 툴킷을 사용할 경우 UI를 위젯의 트리 형태로 인스턴스화 해 초기화할 수 있다. -> xml파일 인플레이팅
몰랐던 사실인데, 이때 각 위젯들(TextView, ImageView)의 경우 내부 상태를 갖고 유지되며, 상호작용을 할 수 있도록 getter / setter를 제공한다.
반면, 컴포즈에서는 선언적 접근 방식으로 위젯이 상태를 저장하지 않고 getter / setter를 제공하지 않는다. 위젯이 객체로 노출 되는 것이 아니라 동일한 컴포저블 함수를 다른 매개변수를 활용해 호출해 UI를 업데이트 한다고 한다.
-> 위의 Greeting이라는 컴포저블 함수의 매개변수 name을 계속 변경해주면서 업데이트!
사용자가 UI와 상호작용할 때, UI에서 onClick과 같은 이벤트를 발생시킨다.
이러한 이벤트를 앱 로직에 전달해 앱의 상태를 변경해야 하는데, 위로 Event를 올리면서 상태가 변경되면 컴포저블 함수는 새 데이터와 함께 다시 호출된다.
-> 데이터가 변경되고, 다시 호출되기 때문에 UI 요소가 다시 그려지게 된다. 이러한 프로세스를 재구성(Recomposition) 이라고 한다.
동적 콘텐츠
컴포저블 함수는 xml이 아니라 코틀린으로 작성되기 때문에, 아래와 같이 코틀린 코드를 작성하여 동적으로 UI를 구성할 수 있다.
@Composable
fun Greeting(names: List<String>) {
for (name in names) {
Text("Hello $name")
}
}
이렇게 작동한다면 Text 위젯이 여러개 나올 것이다!
반복문 뿐만 아니라 if를 사용해 특정 UI 요소를 표시할지 안할지를 결정할 수 있다.
재구성(Recomposition)
xml을 사용했을 때는 수동적으로 setText()를 활용 즉 setter를 호출해 내부 상태를 변경해야 한다.
하지만, Compose에서는 새 데이터를 사용하여 컴포저블 함수를 다시 호출한다.
이런 방식으로 할 경우 함수가 재구성되고, 필요에 따라 함수에서 내보낸 위젯이 새 데이터로 다시 그려진다.
-> 재구성!
@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
Button(onClick = onClick) {
Text("I've been clicked $clicks times")
}
}
위에 코드는 버튼을 표시하는 컴포저블 함수가 있다.
버튼이 클릭될 때마다, 호출자는 clicks 값을 변경한다. 이때, 새로운 값을 보여주기 위해 Text를 호출한다. 즉, UI가 갱신되는데 이 변경과 상관없는 다른 함수들은 재구성하지 않는다.
재구성에 대해 다시 정리하자면,
입력이 변경되었을 때 컴포저블 함수를 다시 호출하는 프로세스이다.
컴포즈에서는 새로운 입력 데이터를 기준으로, 변경될 수 있는 함수 또는 람다만 호출하고 나머지는 생략한다. 이를 바탕으로 효율적으로 재구성을 하며, 컴퓨팅 성능 및 배터리 수명 비용을 줄일 수 있다!
컴포저블 함수의 유의사항
-> 컴포저블 함수는 순서와 관계없이 실행 될 수 있다.
-> 컴포저블 함수는 동시에 실행할 수 있다. = 병렬성
-> 재구성은 최대한 많은 수의 컴포저블 함수와 람다를 건너뛴다.
-> 재구성은 취소될 수 있다.
-> 재구성은 애니메이션의 모든 프레임에서와 같은 빈도로 자주 실행될 수 있다.
(마지막의 경우, 해결하기 어렵다고 한다... 주변 지인 왈)
각 유의사항에 대해서는 코드를 적용해보면서 느낄 수 있을 것 같다!
컴포즈 제대로 익혀봅시다
'Android > Android Compose' 카테고리의 다른 글
[Android] Compose - (3) 상태를 호이스팅할 대상 위치 (0) | 2024.08.19 |
---|---|
[Android] Compose UI - (3) 페이저(Pager) (1) | 2024.08.15 |
[Android] Compose UI - (2) 수정자(Modifier) (0) | 2024.08.14 |
[Android] Compose - (2) 컴포저블 상태 관리 개요 (0) | 2024.08.11 |
[Android] Compose UI - (1) 앱 레이아웃 (0) | 2024.08.10 |