선언형 UI 프레임워크 란?
- 2017년 5월 구글, 크로스 플랫폼 프레임워크 플러터 공개.
- 2019년 6월 애플, UI 개발을 위한 프레임워크 Swift UI 추가.
- 2020년 1월 구글, Jetpack Compose 0.1.0-dev04 버전 공개.
→ 선언형(Declarative) UI 패러다임이 적용되고 있음
명령형 UI 방식
// Imperative style
b.setColor(red)
b.clearChildren()
ViewC c3 = new ViewC(...)
b.add(c3)
이미 그려진 뷰의 속성을 수정한다.
Android 의 기존 작업 방식
1. 기획서를 보고 레이아웃 XML을 만든다.
2. 관련된 스타일 Resource 를 작업한다.
3. XML을 코드로된 뷰에서 로드하고, 각 뷰에 데이터를 바인딩한다.
→ 하나의 화면을 그리기 위해 필요한 파일이 많고, 작성해야하는 라인수도 적지 않음
→ 특히 XML 작성에 많은 시간이 소요되며, 재작업 도 빈번하게 일어나는 편임
선언형 UI 의 방식에는 State 가 존재한다.
// Declarative style
return ViewB(
color: red,
child: ViewC(...),
)
상태 즉 “무엇을” 렌더링할지 정의해주면 자세한 부분은 프레임워크에서 처리
상태 값이 변경되면 새로운 화면을 다시 생성(Recomposition) 하는 방식
선언형의 공식
- 같은 상태에 대해서는 항상 똑같은 View를 리턴
- UI 는 뷰 단위로 구성 (플러터에서는 위젯 Compose 에서는 @Composable 함수)
- 이 뷰는 부모 자식간의 관계를 형성 (플러터에서는 위젯트리, Compose 에서는 펑션트리)
Compose 개요
- 앱 UI를 렌더링할 수 있게 하는 선언형 API를 제공
- 앱 UI를 더 쉽게 작성하고 유지관리할 수 있도록 지원
→ 개발자들의 UI 작업의 생산성을 높여줄 수 있음.
- 현재 1.1.1 버전까지 릴리즈 되었으며, 계속해서 업데이트 될 예정
- 여러 UI 라이브러리들이 이미 Compose 를 지원하고 있음
→ 앞으로 업데이트나 개선이 멈추는 일은 없어 보임
Compose 기본
1. Composable 함수
- @Composable이 함수가 데이터를 UI로 변환하기 위한 함수라는 것을 Compose 컴파일러에 알려줌
- 매개변수를 받을 수 있으며, 아무것도 반환하지 않음(화면에 대한 작업이므로)
- 이 함수는 멱등원이며 부작용이 없음 → 항상 같은 방식으로 동작한다.
2. 동적 컨텐츠
@Composable
fun Greeting(names: List<String>) {
for (name in names) {
Text("Hello $name")
}
}
3. Recomposition
- 재구성은 입력이 변경될 때 구성 가능한 함수를 다시 호출하는 프로세스
- 변경되었을 수 있는 함수 또는 람다만 호출하고 나머지는 패스한다
- 실행이 될 수도 있고 아닐 수도 있다 → 중요한 로직, 공유변수가 컴포저블의 실행에 의존해서는 안된다.
- 공유 객체의 속성, 공유 환경 값(Preferences) 을 write 하지 않아야한다.
4. Recomposition skips as much as possible
/**
* 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) }))
}
- 기존 방식에서 화면 업데이트 성능 향상을 위해 뷰를 플랫하게(ConstraintLayout) 작성하는것과 다르다. 컴포즈에서는 해당 블럭 자체를 건너띄우고 상태가 변경된 것만 갱신할 수 있다.
5. Jetpack Compose Phases
- 컴포지션: 표시할 UI입니다. Compose는 구성 가능한 함수를 실행하고 UI 설명을 만듭니다.
- 레이아웃: UI를 배치할 위치입니다. 이 단계는 측정과 배치라는 두 단계로 구성됩니다. 레이아웃 요소는 레이아웃 트리에 있는 각 노드의 레이아웃 요소 및 모든 하위 요소를 2D 좌표로 측정하고 배치합니다.
- 그리기: 렌더링하는 방법입니다. UI 요소는 일반적으로 기기 화면인 캔버스에 그려집니다.
Compose 장점
1. 빠른 개발 속도, 코드 감소, 유연한 유지 보수
- 컴포즈가 아닌 방식으로 개발하려고 했다면, 아마도 꽤 많은 코드를 작성해야 했을 것이다.
2. View(Model) - Layout 간의 결합도를 낮추고 응집도를 높힌다.
- 결합도 : 결합도는 서로 다른 모듈 간에 상호 의존하는 정도 또는 연관된 관계를 의미 → 최소화
- 응집도 : 응집도는 한 모듈 내부의 처리 요소들이 서로 관련되어 있는 정도 → 최대화
- Android 에서는 일반적으로 이런 구조가 된다. 더군다나 xml 은 여러개 만들어야할 수도 있다.
- UI 를 소스코드와 같은 언어로 작성하고, 결합도를 낮추고 응집력은 높은 구조를 만든다.
3. UI객체를 다시 생성 하므로 개발자가 변수와 UI객체간의 연결고리에 대하여 고민할 필요가 없다.
- FindViewById, Butterknife, ViewBinding, DataBinding … 등에서 벗어난다.
4. 용량, 빌드 타임 감소
- Compose Only 사용하는 경우에만 장점이 될 수 있다
5. 프레임워크에 기반한 성능
- Smart recompositions : Compose는 업데이트해야 하는 부분만 재구성하기 위해 최선을 다합니다.
6. Compose 로 리스트 만들기 예시
https://developer.android.com/jetpack/compose/lists?hl=ko
Compose를 사용하기 위해 알아야 할 것들
1. 상태 관리
- Compose는 선언적. Compose를 업데이트하는 유일한 방법은 새 인수로 동일한 컴포저블을 호출
- 인수 값 → UI 상태 값. 이 값의 관리가 필요함
2. 수명주기
3. 부수효과
4. 단계
Compose 예제 살펴보기
- JetNews
- JetNews 에서 상태관리에 사용한 StateFlow 이해하기
https://developer.android.com/kotlin/flow/stateflow-and-sharedflow?hl=ko
Compose 사용의 이슈들
1. 쉬운데 어렵다.
- 처음 보고 화면 몇장 그렸을때는 쉬운데 본격적으로 쓰고자 한다면? 아 이거 쉽지 않다.
- 제대로 성능 내려면 Compose Only 여야하는데, 꽤 깊은 수준의 이해가 필요하다.
2. 지금까지 알아왔던(?) 많은 것을 버려야한다.
- 기존에 사용했던 XML과 바인딩 도구들. 여러 기본 뷰들과 레이아웃. 제플린이나 피그마를 보면서 아마도 수많은 화면들을 만들었고 그에 따른 여러가지 이슈를 해결해 왔을 것이다.
- 하지만 Compose 를 도입하면서 많은 것들을 버려야할 수 있다.
- 개발자들은 이것을 받아들일수 있을 것인가?
- 뷰 뿐만이 아니다. 앱 구조까지 많은 부분이 고민되어야하고 변경 되어야한다.
3. 우리가 쳐야 되는 디자인은 머테리얼 디자인이 아니다.
- 플러터와 마찬가지로 Compose 또한 머테리얼 디자인 테마와 위젯들을 기본으로 제공한다. 하지만 우리가 다니고 있는 회사에서는 그렇게 심플한 디자인이 나오지 않는다.
- 그렇다. 프로덕트의 디자인은 복잡하다. 앱 개발을 하며 커스텀 뷰나 컴포넌트를 만드는일 이 꽤 많고, 이런 작업들은 사실 쉽지 않다. 그런데 컴포즈가 이런 작업을 더 쉽게 만들어주는가? → 아닌것 같다.
4. Android 라이프 사이클에 대한 연결이 어려움
- Composable 안에서 Activity lifeCycle의 상태를 알수 없다.
- 각 라이프사이클 콜백에서 라이프사이클 상태를 변경해주는 식으로 구현
Compose best practice for performance
1.Release 모드에서 R8 난독화를 사용한다.
- 최적화를 통해서 앱을 더 빠르게 할것이다. Compose 를 사용하는 경우에도.
- 뭔가 디버그모드에서 성능이 안나오는 숨은 버그같은게 있는건가?
2. Remember 함수의 사용
- 아래 경우 항상 재실행된다. 왜냐면 우리의 소팅이 스콥 내부에 있다. 이것은 매번 recomposition 될때마다 불리게 될것이다. 리멤버 함수를 이용해서 소팅로직을 분리하면 이런 이슈를 해결할 수 있다.
- remember를 사용하여 계산결과를 저장한다. 계산이 한 번 실행되고 필요할 때마다 결과를 가져온다.
3. 역방향 쓰기 방지
- ‘이미 읽힌 상태에 쓰지 않는다’ 를 지킨다
@Composable
fun BadComposable() {
var count by remember { mutableStateOf(0) }
// Causes recomposition on click
Button(onClick = { count++ }, Modifier.wrapContentSize()) {
Text("Recompose")
}
Text("$count")
count++ // Backwards write, writing to state after it has been read
}
좋은 샘플 코드들
GitHub - android/compose-samples: Official Jetpack Compose samples.
참조 :
스터디 그룹
'안드로이드' 카테고리의 다른 글
ScopeStorage 저장공간에 대한 정의 (0) | 2022.11.14 |
---|---|
Bitrise-android (0) | 2022.11.14 |
STT관련 라이브러리 API정보 (0) | 2022.11.09 |
STT (구글) - 샘플코드 (0) | 2022.11.09 |
STT(구글) - SpeechRecognizer Document (0) | 2022.11.09 |