https://github.com/tmdgh1592/Jetpack-Compose-Android-Examples
GitHub - tmdgh1592/Jetpack-Compose-Android-Examples: Learn Jetpack Compose for Android by Examples. Learn how to use Jetpack Com
Learn Jetpack Compose for Android by Examples. Learn how to use Jetpack Compose for Android App Development. Android’s modern toolkit for building native UI. - GitHub - tmdgh1592/Jetpack-Compose-An...
github.com
컴포즈 공부 내용은 위 깃허브 레퍼지토리에 정리합니다.
이번 포스팅에서는 Compose API 가이드라인을 살펴보면서 Do & Dont (두스앤돈스) 코드를 공부하려고 합니다.
Do 코드는 해당 라이브러리를 제공해주는 측에서 권장하는 방식의 코드 작성법입니다.
반대로 Don't 코드는 권장하지 않는 코드 작성법입니다.
■ SingleTons, Constants, Sealed Class, Enum Class
기존에 상수나 Enum 클래스를 작성할 때에 CAPITAL_AND_UNDERSCOPES 방식을 선호하였습니다.
하지만 컴포즈에서는 이러한 방식대신에 PascalCase를 사용하도록 권장합니다.
Do
const val DefaultKeyName = "__defaultKey"
val StructurallyEqual: ComparisonPolicy = StructurallyEqualsImpl(...)
object ReferenceEqual : ComparisonPolicy {
// ...
}
sealed class LoadResult<T> {
object Loading : LoadResult<Nothing>()
class Done(val result: T) : LoadResult<T>()
class Error(val cause: Throwable) : LoadResult<Nothing>()
}
enum class Status {
Idle,
Busy
}
Don't
const val DEFAULT_KEY_NAME = "__defaultKey"
val STRUCTURALLY_EQUAL: ComparisonPolicy = StructurallyEqualsImpl(...)
object ReferenceEqual : ComparisonPolicy {
// ...
}
sealed class LoadResult<T> {
object Loading : LoadResult<Nothing>()
class Done(val result: T) : LoadResult<T>()
class Error(val cause: Throwable) : LoadResult<Nothing>()
}
enum class Status {
IDLE,
BUSY
}
■ @Composable Entity 네이밍 & Naming CompositionLocals
일반적인 함수(메서드)에서는 네이밍을 할 때, 소문자로 시작하여 PascalCase로 작성하는 경우가 일반적입니다. 또한 단순히 함수명은 명사가 아닌 동사로 시작하는 경우가 많았습니다.
예를 들어, 메모장에 무언가를 작성해주는 함수를 만든다면, fun writeText() 와 같은 명령문구였습니다.
하지만 @Composable 함수를 만들 때에는 이와 정 반대로, 대문자로 시작하며, 동사보다는 명사를 사용하기를 권장합니다.
추가적으로, 네이밍에 형용사가 들어가는 경우, 실제 영어 문법처럼 명사의 앞(Pre)에 형용사를 작성해주어야 합니다.
Do
// This function is a descriptive PascalCased noun as a visual UI element
@Composable
fun FancyButton(text: String, onClick: () -> Unit) {
// This function is a descriptive PascalCased noun as a non-visual element
// with presence in the composition
@Composable
fun BackButtonHandler(onBackPressed: () -> Unit) {
// "Local" is used here as an adjective, "Theme" is the noun.
val LocalTheme = staticCompositionLocalOf<Theme>()
Don't
// This function is a noun but is not PascalCased!
@Composable
fun fancyButton(text: String, onClick: () -> Unit) {
// This function is PascalCased but is not a noun!
@Composable
fun RenderFancyButton(text: String, onClick: () -> Unit) {
// This function is neither PascalCased nor a noun!
@Composable
fun drawProfileImage(image: ImageAsset) {
// "Local" is used here as a noun!
val ThemeLocal = staticCompositionLocalOf<Theme>()
■ @Composable 값을 반환하는 함수 네이밍 &
Jetpack Compose는 Kotlin Coding Conventions for the naming of function 즉, 코틀린 네이밍 컨벤션을 따로라고 명시되어 있습니다.
위에서는 대문자, 명사로 시작하라고 했으면서 왜 갑자기 소문자인지 의구심이 생길 수 있습니다.
위에서 다룬 컴포즈 함수는 화면을 직접적으로 그리는 함수이지만, 현재 다루는 함수는 style, resourceId와 같은 값(value)를 반환해주는 함수이기 때문입니다.
Do
// Returns a style based on the current CompositionLocal settings
// This function qualifies where its value comes from
@Composable
fun defaultStyle(): Style {
Don't
// Returns a style based on the current CompositionLocal settings
// This function looks like it's constructing a context-free object!
@Composable
fun Style(): Style {
■ @Composable functions that remember {} the objects they return
또한, 코루틴 스코프와 같은 객체를 반환하는 함수는 rememberAObject() 라는 방식으로 네이밍하라고 명시되어 있습니다. 반례로 createAObject(), makeAObject() 와 같은 네이밍이 있는데, 이러한 방식이 아닌 remember로 통일하는 듯한 모습입니다.
Do
// Returns a CoroutineScope that will be cancelled when this call
// leaves the composition
// This function is prefixed with remember to describe its behavior
@Composable
fun rememberCoroutineScope(): CoroutineScope {
Don't
// Returns a CoroutineScope that will be cancelled when this call leaves
// the composition
// This function's name does not suggest automatic cancellation behavior!
@Composable
fun createCoroutineScope(): CoroutineScope {
■ Emit XOR return a value
@Composable 함수는 컨텐츠를 컴포지션으로 내보내거나, 값을 반환하지만 이 두가지를 동시에 해서는 안 된다고 합니다.
이 부분은 정확히 이해를 못했지만, 코드의 차이를 들여다보면,
Do 코드의 경우, 사용자 입력값과 같은 inputState 변수를 remember를 통해 따로 저장하여 상태를 관리하고 있고,
Don't 코드의 경우, InputField() 메서드가 UserInputState형 객체를 반환하고 있습니다. 즉, inputState 변수를 한 번 초기화하고 더 관리하지 않기 때문에, 주석에 작성되어 있는 것처럼 InputField와의 통신이 어려워집니다.
Do
// Emits a text input field element that will call into the inputState
// interface object to request changes
@Composable
fun InputField(inputState: InputState) {
// ...
// Communicating with the input field is not order-dependent
val inputState = remember { InputState() }
Button("Clear input", onClick = { inputState.clear() })
InputField(inputState)
Don't
// Emits a text input field element and returns an input value holder
@Composable
fun InputField(): UserInputState {
// ...
// Communicating with the InputField is made difficult
Button("Clear input", onClick = { TODO("???") })
val inputState = InputField()
interface DetailCardState {
val actionRailState: ActionRailState
// ...
}
@Composable
fun DetailCard(state: DetailCardState) {
Surface {
// ...
ActionRail(state.actionRailState)
}
}
@Composable
fun ActionRail(state: ActionRailState) {
// ...
}
■ Compose UI API structure
Compose UI는 Compose 런타임에 빌드된 UI 도구 키트입니다. 이 섹션에서는 Compose UI 도구 키트를 사용하고 확장하는 API에 대한 지침을 간략하게 설명합니다.
예시) 아래 코드와 같이 UI를 만들면 됩니다.
@Composable
fun SimpleLabel(
text: String,
modifier: Modifier = Modifier
) {
■ Element는 Unit을 반환합니다.
여기서 Element는 @Composable Annotation이 붙은 UI 구성 요소라고 생각하겠습니다.
요소는 emit()을 호출하거나 다른 Compose UI 요소 함수를 호출하여 루트 UI 노드를 직접 방출해야 합니다(MUST). 값을 반환해서는 안 됩니다(MUST NOT).
그 이유는 Element는 Compose UI 구성의 선언적(선언형) 엔티티이기 때문입니다. 컴포지션에서의 존재 여부에 따라 결과 UI에 표시되는 여부가 결정되기 때문에, 값을 반환하지 않습니다.
만약 Element에 대한 이벤트 처리를 해야 한다면, 매개변수를 통해 이벤트 제어 관련 함수나 객체를 입력받도록 권장합니다.
Do
@Composable
fun FancyButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Don't
interface ButtonState {
val clicks: Flow<ClickEvent>
val measuredSize: Size
}
@Composable
fun FancyButton(
text: String,
modifier: Modifier = Modifier
): ButtonState {
■ Elements accept and respect a Modifier parameter
Element는 Modifier 파라미터를 입력받으라고 합니다.
Modifier의 경우, Compose UI Element 내에서 파라미터의 defaultValue로 값을 지정해주거나, 외부에서 입력받을 수 있습니다.
만약 외부에서 입력을 받으면, 중복되는 Element 속성들을 통일화하여 보일러 플레이트 코드를 줄일 수 있고, 외부에서 조작하기에 유리합니다.
Do
@Composable
fun FancyButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier
) = Text(
text = text,
modifier = modifier.surface(elevation = 4.dp)
.clickable(onClick)
.padding(horizontal = 32.dp, vertical = 16.dp)
)
■ Compose UI layouts
하나 이상의 @Composable 함수 매개변수를 허용하는 Compose UI 요소를 레이아웃이라고 합니다.
Composable 안에 Composable을 넣어서 레이아웃을 따로 만들 수 있습니다.
이 때, 매개변수의 이름은 "content" 로 작성하며, Kotlin의 후행 람다 구문을 사용할 수 있도록 @Composable 함수 매개변수를 마지막 위치에 배치해야 합니다.
다만, 문서상에 (SHOULD) 라고 작성되어 있는 것으로 보아, 필수가 아닌 "권장사항" 인듯 하나, 기본에 충실하도록 권장사항을 따르겠습니다.
@Composable
fun SimpleRow(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
// we can use like this.
// SimpleRow(FancyButton())
포스팅이 길어지는 관계로 다음 포스팅에 이어서 작성하도록 하겠습니다.
'Android > Compose' 카테고리의 다른 글
[Android] Jetpack Compose Part 1 - Does and Dont Code Style #2 (0) | 2022.05.16 |
---|---|
[Android] Jetpack Compose Part 0 - Compose란? (0) | 2022.05.04 |