Android-jetpack Compose
jackpack compose - Api 가이드라인
slow333
2023. 3. 18. 10:30
GitHub - androidx/androidx: Development environment for Android Jetpack extension libraries under the androidx namespace. Synchr
Development environment for Android Jetpack extension libraries under the androidx namespace. Synchronized with Android Jetpack's primary development branch on AOSP. - GitHub - androidx/android...
github.com
정리한 내용
https://spangle-wedelia-2dc.notion.site/Compose-Api-105b1694af3a48a38aedc1977fc59918
Compose Api 가이드라인
원문
spangle-wedelia-2dc.notion.site
파스칼 케이스의 사용
#파스칼케이스
PascalCase
#케피탈케이스
CAPITALS_AND_UNDERSCORES
▪️@Composable 엔티티 - 이름은 대문자로 시작 - 명사
#콤포저블 은 명사로
// 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) {}
// This function is PascalCased but is not a noun!
@Composable
fun MyButton(text: String, onClick: () -> Unit) {
// This function is neither PascalCased nor a noun!
@Composable
fun ProfileImage(image: ImageAsset) {
▪️@Composable 값을 반환하는 콤포저블
// Returns a style based on the current CompositionLocal settings
// This function qualifies where its value comes from
@Composable
fun defaultStyle(): Style {
// Returns a style based on the current CompositionLocal settings
// This function looks like it's constructing a context-free object!
@Composable
fun selectedStyle(): Style {
▪️**@Composable functions that remember {} the objects they return**
// 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 {
▪️콤포지션 로컬 이름
// "Local" is used here as an adjective, "Theme" is the noun.
val LocalTheme = staticCompositionLocalOf<Theme>()
// "Local" is used here as a noun!
val GlobalTheme = staticCompositionLocalOf<Theme>()
▪️Emit XOR return a value
상태는 매개변수로 콤포저블에 넣으세요
// 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)
# 이렇게 하지 마세요
// 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()
Communicating with a composable by passing parameters forward affords aggregation of several such parameters into types used as parameters to their callers:
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 elements
@Composable
fun SimpleLabel(
text: String,
modifier: Modifier = Modifier
) {
## 이벤트 처리는 lambda unit 등으로 처리 권장
@Composable
fun FancyButton(
text: String,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
## 이렇게 하지 마세요
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
모디파이어 매개변수로 넣으세요
@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
fun SimpleRow(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
## example
SimpleRow(FancyButton())
Compose UI modifiers
# Modifier factory functions
Modifier.preferredSize(50.dp)
.backgroundColor(Color.Blue)
.padding(10.dp)
fun Modifier.myModifier(
param1: ...,
paramN: ...
): Modifier = then(MyModifierImpl(param1, ... paramN))
Composed modifiers
모디파이어 여러개 적용
fun Modifier.myModifier(): Modifier = composed {
val color = LocalTheme.current.specialColor
backgroundColor(color)
}
fun Modifier.modifierWithState(): Modifier = composed {
val elementSpecificState = remember { MyModifierState() }
MyModifier(elementSpecificState)
}
// ...
val myModifier = someModifiers.modifierWithState()
Text("Hello", modifier = myModifier)
Text("World", modifier = myModifier)
Layout-scoped modifiers
@Stable
interface WeightScope {
fun Modifier.weight(weight: Float): Modifier
}
@Composable
fun WeightedRow(
modifier: Modifier = Modifier,
content: @Composable WeightScope.() -> Unit
) {
// ...
// Usage:
WeightedRow {
Text("Hello", Modifier.weight(1f))
Text("World", Modifier.weight(2f))
}
Compose API design patterns
콤포즈로 UI 짤때 자주 쓰이는 패턴들
@Composable
fun Checkbox(
isChecked: Boolean,
onToggle: () -> Unit
) {
// ...
// Usage: (caller mutates optIn and owns the source of truth)
Checkbox(
myState.optIn,
onToggle = { myState.optIn = !myState.optIn }
)
#Don't 이렇게 하지 마세요
@Composable
fun Checkbox(
initialValue: Boolean,
onChecked: (Boolean) -> Unit
) {
var checkedState by remember { mutableStateOf(initialValue) }
// ...
// Usage: (Checkbox owns the checked state, caller notified of changes)
// Caller cannot easily implement a validation policy.
Checkbox(false, onToggled = { callerCheckedState = it })
Separate state and events
Hoisted state types
# 이전
@Composable
fun VerticalScroller(
scrollPosition: Int,
scrollRange: Int,
onScrollPositionChange: (Int) -> Unit,
onScrollRangeChange: (Int) -> Unit
) {
# 이렇게 하시오
@Stable
interface VerticalScrollerState {
var scrollPosition: Int
var scrollRange: Int
}
@Composable
fun VerticalScroller(
verticalScrollerState: VerticalScrollerState
) {
Default policies through hoisted state objects
fun VerticalScrollerState(): VerticalScrollerState =
VerticalScrollerStateImpl()
private class VerticalScrollerStateImpl(
scrollPosition: Int = 0,
scrollRange: Int = 0
) : VerticalScrollerState {
private var _scrollPosition by
mutableStateOf(scrollPosition, structuralEqualityPolicy())
override var scrollPosition: Int
get() = _scrollPosition
set(value) {
_scrollPosition = value.coerceIn(0, scrollRange)
}
private var _scrollRange by
mutableStateOf(scrollRange, structuralEqualityPolicy())
override var scrollRange: Int
get() = _scrollRange
set(value) {
require(value >= 0) { "$value must be > 0" }
_scrollRange = value
scrollPosition = scrollPosition
}
}
@Composable
fun VerticalScroller(
verticalScrollerState: VerticalScrollerState =
remember { VerticalScrollerState() }
) {
# don't 이렇게 하지 마시오
// Null as a default can cause unexpected behavior if the input parameter
// changes between null and non-null.
@Composable
fun VerticalScroller(
verticalScrollerState: VerticalScrollerState? = null
) {
val realState = verticalScrollerState ?:
remember { VerticalScrollerState() }
Default hoisted state for modifiers
fun Modifier.foo() = composed {
FooModifierImpl(remember { FooState() }, LocalBar.current)
}
fun Modifier.foo(fooState: FooState) = composed {
FooModifierImpl(fooState, LocalBar.current)
}
# don't 이렇게 하지마세요
// Null as a default can cause unexpected behavior if the input parameter
// changes between null and non-null.
fun Modifier.foo(
fooState: FooState? = null
) = composed {
FooModifierImpl(
fooState ?: remember { FooState() },
LocalBar.current
)
}
// @Composable modifier factory functions cannot be used
// outside of composition.
@Composable
fun Modifier.foo(
fooState: FooState = remember { FooState() }
) = composed {
FooModifierImpl(fooState, LocalBar.current)
}
Extensibility of hoisted state types
// Defined by another team or library
data class PersonData(val name: String, val avatarUrl: String)
class FooState {
val currentPersonData: PersonData
fun setPersonName(name: String)
fun setPersonAvatarUrl(url: String)
}
// Defined by the UI layer, by yet another team
class BarState {
var name: String
var avatarUrl: String
}
@Composable
fun Bar(barState: BarState) {
## 이렇게 사용하세요
@Stable
interface FooState {
val currentPersonData: PersonData
fun setPersonName(name: String)
fun setPersonAvatarUrl(url: String)
}
@Stable
interface BarState {
var name: String
var avatarUrl: String
}
class MyState(
name: String,
avatarUrl: String
) : FooState, BarState {
override var name by mutableStateOf(name)
override var avatarUrl by mutableStateOf(avatarUrl)
override val currentPersonData: PersonData =
PersonData(name, avatarUrl)
override fun setPersonName(name: String) {
this.name = name
}
override fun setPersonAvatarUrl(url: String) {
this.avatarUrl = url
}
}
## 팩토리의 사용
@Stable
interface FooState {
// ...
}
fun FooState(): FooState = FooStateImpl(...)
private class FooStateImpl(...) : FooState {
// ...
}
// Usage
val state = remember { FooState() }