-
Notifications
You must be signed in to change notification settings - Fork 2
Jetpack Compose Convention
feature:xxx
โโ ๐ navigation
โโ ๐ ui
โโ ๐ component
โโ ๐ Screen
โโ ๐ UiEvent
โโ ๐ UiState
โโ ๐ ViewModel
โโ ...Bottari
โโ app // ์ฑ ์ง์
์
โโ build-logic // ๊ณตํต Gradle ์ค์ , ํ๋ฌ๊ทธ์ธ ์ ์
โโ core
โ โโ core:designsystem // ์์ยทํ์ดํฌยท์ปดํฌ๋ํธ ๋ฑ ๊ณตํต ๋์์ธ ์์คํ
โ โโ core:ui // FadeEdge, Sheet ๋ฑ ๋๋ฉ์ธ ๋
๋ฆฝ UI ์ ํธ
โ โโ core:model // ์์ Kotlin ๋ฐ์ดํฐ ํด๋์ค ์ ์
โ โโ core:domain // UseCase ๋ฑ ๋น์ฆ๋์ค ๋ก์ง ์ธํฐํ์ด์ค
โ โโ core:data // Repository ๊ตฌํ ๋ฐ ๋ฐ์ดํฐ ์กฐํฉ ๊ณ์ธต
โ โโ core:network // RetrofitยทOkHttp ๊ธฐ๋ฐ API ํต์ ์ ์
โ โโ core:local // RoomยทDataStore ๋ฑ ๋ก์ปฌ ์ ์ฅ ๊ตฌํ
โโ feature
โโ feature:main // ์ฒซ ์ง์
+ ๋ฉ์ธ NavHost
โโ ... (๊ธฐ๋ฅ๋ณ ์ถ๊ฐ)Screen โ ScreenContent โ Section(Header, Body โฆ) โ Element(Button, Image โฆ)
๊ท์น 1. ์ญํ ์ด ๋ ๊ฐ ์ด์ ๋ณด์ด๋ฉด ์๋ผ๋ธ๋ค (๋จ์ผ ์ฑ ์)
๊ท์น 2. ๋์ผ/์ ์ฌํ UI๊ฐ 2๋ฒ ์ด์ ๋ฑ์ฅํ๋ฉด ๋ถ๋ฆฌ (Common or Element)
๊ท์น 3. ์ค์ฒฉ ๊น์ด ๊ธฐ์ค
Column {
// ์ฌ๊ธฐ ๋ถ๋ฆฌ
Row {
Column {
Row { ... }
Text()
}
}
Text()
}`๊ท์น
- UI๋ฅผ ๊ทธ๋ฆฌ๋ @Composable ํจ์ ์ด๋ฆ์ PascalCase + ๋ช ์ฌํ์ผ๋ก ์์ฑํ๋ค.
- ๋ฐํ๊ฐ์ด Unit์ด๋ฉด ๋ช ์ฌํ์ผ๋ก, ๊ฐ์ ๋ฐํํ๋ค๋ฉด ๋์ฌํ์ผ๋ก ์์ฑํด๋ ๋๋ค.
์ด์
- Composable์ โUI ๊ฐ์ฒด ์์ฑ์โ์ฒ๋ผ ๋์ํ๋ฏ๋ก ๋ช ์ฌํ์ด ๋ ์ง๊ด์ ์ด๋ค.
- Kotlin ๋ค์ด๋ฐ ์ปจ๋ฒค์ ๊ณผ Material ๋ผ์ด๋ธ๋ฌ๋ฆฌ(Button, Card, Text) ์คํ์ผ์ ๋ฐ๋ฅธ๋ค.
// โ
์ฌ๋ฐ๋ฅธ ์์
@Composable
fun ProfileCard(userName: String, modifier: Modifier = Modifier) {
Text(text = userName)
}
// ๐ซ ์๋ชป๋ ์์
@Composable
fun drawProfileCard(userName: String) { // ๋์ฌ ์์ X
Text(text = userName)
}๊ท์น
- ์ปดํฌ์ ๋ธ ํจ์์ ์ธ์๋ฅผ ์ ๋ฌํ ๋ ํ๋ผ๋ฏธํฐ ์์๋ฅผ ์ค์ํ๋ค.
- ํ์ ํ๋ผ๋ฏธํฐ โ Modifier โ ์ ํ์ ํ๋ผ๋ฏธํฐ
- ํ๋ผ๋ฏธํฐ๊ฐ ์ ์ธ๋ ์์๋๋ก ์ ๋ฌํ๋ค.
์ด์
-
ํต์ฌ ๋ฐ์ดํฐ, ๋ ์ด์์ ๊ด๋ จ ์ค์ , UI ์ต์ ์์๋๋ก ์์ฑํ๋ฉด โ๋ฌด์์ ๋ณด์ฌ์ค์ง, ์ด๋ป๊ฒ ๋ฐฐ์นํ ์ง, ์ด๋ค ์ํ๋ก ๋ณด์ฌ์ค์งโ ๋ผ๋ ๋ ผ๋ฆฌ์ ํ๋ฆ์ ํํํ ์ ์๋ค.
-
์์๊ฐ ํต์ผ๋์ด์ผ ์ฝ๋์ ์ผ๊ด์ฑ์ด ํฅ์๋๋ค.
@Composable fun Text( text: String, modifier: Modifier = Modifier, color: Color = Color.Unspecified, fontSize: TextUnit = TextUnit.Unspecified, fontStyle: FontStyle? = null, fontWeight: FontWeight? = null, fontFamily: FontFamily? = null, letterSpacing: TextUnit = TextUnit.Unspecified, textDecoration: TextDecoration? = null, textAlign: TextAlign? = null, lineHeight: TextUnit = TextUnit.Unspecified, overflow: TextOverflow = TextOverflow.Clip, softWrap: Boolean = true, maxLines: Int = Int.MAX_VALUE, minLines: Int = 1, onTextLayout: ((TextLayoutResult) -> Unit)? = null, style: TextStyle = LocalTextStyle.current ) // โ ์ฌ๋ฐ๋ฅธ ์์ Text( text = "ํ ์คํธ", modifier = modifier, color = Color.Black, maxLines = 1, ) // ๐ซ ์๋ชป๋ ์์ Text( color = Color.Black, modifier = modifier, text = "ํ ์คํธ", maxLines = 1, )
๊ท์น
- ํ๋ผ๋ฏธํฐ ๋ช ์ ์๋ฃํ์ ๋ช ์ํ์ง ์๋๋ค.
์ด์
-
Kotlin์ ํ์ ์ถ๋ก ์ธ์ด์ด๊ธฐ ๋๋ฌธ์, ์๋ฃํ์ด ๋ช ํํ ๊ฒฝ์ฐ ์ด๋ฆ์ ์์ฑํ ํ์๊ฐ ์๋ค.
-
Jetpack Compose๋ ์ ์ธํ UI๋ฅผ ์งํฅํ๊ธฐ ๋๋ฌธ์, โ๋ฌด์์ ๋ณด์ฌ์ฃผ๋๊ฐโ์ ์ง์คํด์ผ ํ๋ค.
// โ ์ฌ๋ฐ๋ฅธ ์์ @Composable fun ItemEditLazyColumn( items: List<PersonalChecklistItemUiModel>, onDeleteClick: (Long) -> Unit, listState: LazyListState, modifier: Modifier = Modifier, ) // ๐ซ ์๋ชป๋ ์์ @Composable fun ItemEditLazyColumn( itemList: List<PersonalChecklistItemUiModel>, onDeleteClick: (Long) -> Unit, listState: LazyListState, modifier: Modifier = Modifier, )
๊ท์น
- ์ด๋ฒคํธ ํธ๋ค๋ฌ (๋๋ค ํจ์) ๋
on + ์ฃผ์ด + ๋์ฌํ์์ผ๋ก ์์ฑํ๋ค.- ๋งค๊ฐ๋ณ์ ๋ฆฌ์คํธ(
())๊ฐ ์์ ์์๊ฐ 2๊ฐ ์ด์์ผ ๋(name: String, id: Long) -> Unitํ์์ผ๋ก ์์ฑํ๋ค.
- ๋งค๊ฐ๋ณ์ ๋ฆฌ์คํธ(
- ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ํธ์ถ๋๋ ๊ณณ์์๋
on + ๋์ฌํ์์ผ๋ก ์์ฑํ๋ค. (์ปดํฌ์ ๋ธ์ด ๋ถ๋ฆฌ๋์ด ์๋ ๊ฒฝ์ฐ)
์ด์
-
โ๋ฌด์ธ๊ฐ ๋ฐ์ํ์ ๋ ํธ์ถ๋๋ ์ฝ๋ฐฑโ์ด๋ฏ๋ก, ๊ทธ ์ด๋ฆ์ ํธ๋ฆฌ๊ฑฐ๋๋ ์์ ์ ๋ช ํํ ๋๋ฌ๋ด์ผ ํ๋ค.
-
Jetpack Compose์ ๊ณต์ ์ปดํฌ๋ํธ๋ ๋ชจ๋ ์ ๊ท์น์ ๋ฐ๋ฅธ๋ค.
-
์ ๋ฌ๋๋ ๋ก์ง์ ์คํํ๋ ๊ฒ ์๋๋ผ ์ธ๋ถ์ ์๋ฆฌ๋ ์ญํ ์ด๋ผ๋ ์ ์ ๋ช ์ํด์ผ ํ๋ค.
// โ ์ฌ๋ฐ๋ฅธ ์์ @Composable fun RepeatDaySelector( alarm: AlarmUiModel, onRepeatDaysChange: (RepeatDayUiModel) -> Unit, modifier: Modifier = Modifier, ) // ๐ซ ์๋ชป๋ ์์ 1 : ๋์ฌ + ์ฃผ์ด ํํ์ธ ๊ฒฝ์ฐ @Composable fun RepeatDaySelector( alarm: AlarmUiModel, onChangeRepeatDays: (RepeatDayUiModel) -> Unit, modifier: Modifier = Modifier, ) // ๐ซ ์๋ชป๋ ์์ 2 : ๋์ฌ๊ฐ ๊ณผ๊ฑฐํ์ธ ๊ฒฝ์ฐ @Composable fun RepeatDaySelector( alarm: AlarmUiModel, onChangedRepeatDays: (RepeatDayUiModel) -> Unit, modifier: Modifier = Modifier, )
๊ท์น
- UI๋ฅผ ์ถ๋ ฅํ๋ ๋ชจ๋ Composable์๋
modifier: Modifier = Modifierํ๋ผ๋ฏธํฐ๋ฅผ ํฌํจํด์ผ ํ๋ค. - ์์น๋ ํ์ ํ๋ผ๋ฏธํฐ ๋ค, ์ฒซ ๋ฒ์งธ ์ ํ์ ํ๋ผ๋ฏธํฐ์ฌ์ผ ํ๋ค.
// โ
์ฌ๋ฐ๋ฅธ ์์
@Composable
fun ProfileCard(
name: String,
modifier: Modifier = Modifier,
) {
Text(
text = name,
modifier = modifier
.padding(8.dp)
.background(Color.LightGray)
)
}
// ๐ซ ์๋ชป๋ ์์ โ modifier ์์น๊ฐ ๋ค์ชฝ
@Composable
fun ProfileCard(
name: String,
backgroundColor: Color = Color.Gray,
modifier: Modifier = Modifier,
) { ... }๊ท์น
-
MutableState๋ State๋ฅผ ์ง์ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ง ์๋๋ค.
-
๋์ ํ์ฌ ๊ฐ + ๋ณ๊ฒฝ ์ฝ๋ฐฑ ํํ๋ก ๋ฐ๋๋ค.
์: (value: String, onValueChange: (String) -> Unit)
์ด์
- ์ํ ์์ ๊ถ์ด ๋ช ํํด์ง๊ณ , ViewModel ๋๋ ์์ ์ปดํฌ๋ํธ๊ฐ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํด์ง๋ค.
- Compose recomposition ์ด ์ต์ ํ๋๋ค.
// โ
์ฌ๋ฐ๋ฅธ ์์
@Composable
fun NameField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
) {
TextField(
value = value,
onValueChange = onValueChange,
modifier = modifier
)
}
// ๐ซ ์๋ชป๋ ์์
@Composable
fun NameField(
nameState: MutableState<String>, // ์ํ ์ง์ ์ ๋ฌ โ
modifier: Modifier = Modifier,
) {
TextField(
value = nameState.value,
onValueChange = { nameState.value = it },
modifier = modifier
)
}๊ท์น
- ๊ธฐ๋ณธ๊ฐ์ Companion object ๋๋ ComponentDefaults ๋ค์์คํ์ด์ค๋ก ๋ถ๋ฆฌํ๋ค.
- null์ โ๊ธฐ๋ณธ๊ฐโ์ผ๋ก ์ฌ์ฉํ์ง ์๋๋ค. null์ โ์์โ์ ์๋ฏธํด์ผ ํ๋ค.
์ด์
- ๋ด๋ถ ๋ก์ง๊ณผ ๊ธฐ๋ณธ๊ฐ ์ ์๋ฅผ ๋ถ๋ฆฌํ๋ฉด ํ ์คํธ์ ์ ์ง๋ณด์๊ฐ ์ฌ์์ง๋ค.
- ๋ช ์์ ๊ธฐ๋ณธ๊ฐ์ ์ฝ๋ ์์ธก ๊ฐ๋ฅ์ฑ์ ๋์ธ๋ค.
object CardDefaults {
val ContainerColor = Color.White
val Shape = RoundedCornerShape(12.dp)
}
@Composable
fun InfoCard(
modifier: Modifier = Modifier,
containerColor: Color = CardDefaults.ContainerColor,
shape: Shape = CardDefaults.Shape,
content: @Composable () -> Unit,
) {
Surface(
modifier = modifier,
color = containerColor,
shape = shape,
) {
content()
}
}๊ท์น
- ๊ฐ๋ฅํ ํ stateless ์ปดํฌ๋ํธ๋ก ์์ฑํ๊ณ ์ํ๋ฅผ ์ธ๋ถ์์ ์ฃผ์ ๋ฐ๋๋ก ์ค๊ณํ๋ค.
์ด์
- ํ ์คํธ ์ฉ์ด์ฑ ํฅ์
- Preview, UI Test, Snapshot Test์์ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ
- ViewModel๊ณผ ๋ช ํํ ๋ถ๋ฆฌ
// โ
Stateless
@Composable
fun Counter(
count: Int,
onIncrement: () -> Unit,
) {
Button(onClick = onIncrement) {
Text("Count: $count")
}
}
// ๐ซ Stateful โ ๋ด๋ถ์์ remember ์ฌ์ฉ (ํธ์ถ์ ์ ์ด ๋ถ๊ฐ)
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}๊ท์น
- ํ์ UI๋ฅผ ์ปค์คํฐ๋ง์ด์งํ ์ฌ์ง๊ฐ ์๋ค๋ฉด @Composable () -> Unit ํํ์ Slot ์ ๊ณต.
- SlotScope (RowScope, BoxScope ๋ฑ)๋ฅผ ํจ๊ป ์ ๋ฌํ๋ฉด ๋์ฑ ์ ์ฐํ๋ค.
์ด์
- Button, Card, Scaffold ๋ฑ๊ณผ ๊ฐ์ ๊ตฌ์กฐ์ ์ปดํฌ๋ํธ๋ ์ฌ๋กฏ์ด ํ์ฅ์ฑ์ ๊ฒฐ์ ํ๋ค.
@Composable
fun AlertDialogCard(
title: String,
modifier: Modifier = Modifier,
content: @Composable ColumnScope.() -> Unit,
) {
Column(
modifier = modifier
.padding(16.dp)
.background(Color.White, RoundedCornerShape(8.dp))
) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Spacer(Modifier.height(8.dp))
content()
}
}๊ท์น
- ์ด๋ฏธ์ง๋ฅผ ํํํ๋ ์ปดํฌ๋ํธ์๋ contentDescription ์์ฑ.
- ์ด๋ฏธ์ง๊ฐ ์์ ํ ์ฅ์์ฉ์ด๊ณ ํด๋ฆญ ๊ฐ๋ฅํ์ง ์๊ฑฐ๋ ์ฝํ ์ธ ์ ๊ด๋ จ์ด ์๋ค๋ฉด contentDescription์ null๋ก ์ค์ .
- ํด๋ฆญ ๊ฐ๋ฅ ์์๋ Modifier.semantics ๋๋ Modifier.clickable ๋ก ์๋งจํฑ ์ ๋ณด ํฌํจ.
์ด์
- ์คํฌ๋ฆฐ ๋ฆฌ๋, ์์ฑ ์๋ด ๋ฑ ๋ณด์กฐ๊ธฐ๊ธฐ ์ ๊ทผ์ฑ ํ๋ณด๋ฅผ ์ํ ์ต์ ์กฐ๊ฑด์ด๋ค.
@Composable
fun Avatar(
imageUrl: String,
contentDescription: String?,
modifier: Modifier = Modifier,
) {
Image(
painter = rememberAsyncImagePainter(imageUrl),
contentDescription = contentDescription,
modifier = modifier
.size(40.dp)
.clip(CircleShape)
)
}๊ท์น
- ๊ธฐ์กด ํ๋ผ๋ฏธํฐ๋ ์ ๋ ์ ๊ฑฐํ์ง ์๋๋ค.
- ์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํ ๋๋ ๋ฐ๋์ ๊ธฐ๋ณธ๊ฐ(default)์ ์ง์ ํ๋ค.
- ํ์ ์ ์ค๋ฒ๋ก๋๋ก ๋์ฒดํ๊ณ , ๊ธฐ์กด ํจ์๋ @Deprecated ์ฒ๋ฆฌํ๋ค.
์ด์
- Compose๋ ํจ์ ๊ธฐ๋ฐ API์ด๋ฏ๋ก, ํ๋ผ๋ฏธํฐ ์ญ์ ๊ฐ ๋ฐ๋ก ์ปดํ์ผ ์๋ฌ๋ก ์ด์ด์ง๋ค.
- ํ์ ํธํ์ฑ์ ๋ณด์ฅํด์ผ ํ ๋ด/์ธ๋ถ ์ฌ์ฉ์ ์ฝ๋๊ฐ ์์ ํ๊ฒ ์ ์ง๋๋ค.
// โ
์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํ ๋ฒ์
@Composable
fun Badge(
count: Int,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Red, // ์๋ก ์ถ๊ฐ๋ ํ๋ผ๋ฏธํฐ
) { ... }
// ๐ซ ๊ธฐ์กด ํจ์๋ฅผ ๋ฐ๋ก ์์ ํ์ง ์๋๋ค
@Deprecated("Use Badge(count, modifier, backgroundColor) instead",
level = DeprecationLevel.HIDDEN)
@Composable
fun Badge(count: Int, modifier: Modifier = Modifier) =
Badge(count, modifier, backgroundColor = Color.Red)Compose Component API Guideline
https://hongbeomi.medium.com/๋ฒ์ญ-compose-api-๊ฐ์ด๋๋ผ์ธ-e6d999408d92
- ํ ๋ฌธํ
- ์๋น์ค ์๊ฐ ๋ฐ ์ฌ์ฉ์ ์๋๋ฆฌ์ค
- Git Convention & ์ ๋ต
- 3์ฐจ ์คํ๋ฆฐํธ์์ ๊ตฌํํ ํต์ฌ ๊ธฐ๋ฅ ๋ชฉ๋ก
- Kotlin Convention
- Resource Naming Convention
- Compose Convention
- ๋ฒ์ ๋ฐ ๊ธฐ์ ์คํ ์ ์
- ์ฝ๋ ๋ฆฌ๋ทฐ ํ๋ก์ธ์ค
- ํ ์คํธ ์ ๋ต
- ์ธ๋ถ ๋ชจ๋ํฐ๋ง ๋๊ตฌ ์ฐ๋
- ๋ก๊น ์ ๋ต
- SSE ํด๋ผ์ด์ธํธ ๊ตฌํ
- ConventionPlugin Guide
- Java Convention
- ๋ฒ์ ๋ฐ ๊ธฐ์ ์คํ ์ ์
- ๊ฐ๋ฐ ์๋ฒ ์์ ๋ด์ฉ ๋ฐ์ ๋ฐฉ๋ฒ
- ๊ฐ๋ฐ ์๋ฒ ๋ฐฐํฌ ์๋ํ ํ์ดํ๋ผ์ธ
- ๋ฌด์ค๋จ ๋ฐฐํฌ ์๋ํ ํ์ดํ๋ผ์ธ ๊ตฌ์ถ
- ์ด์ ์๋ฒ ์ธํ๋ผ ์ํคํ ์ฒ
- ๋ชจ๋ํฐ๋ง ๋๊ตฌ ์ ํ ์ด์
- ํ ๋ณด๋ฐ๋ฆฌ ๋ณ๊ฒฝ ์ฌํญ ์ค์๊ฐ ๋ฐ์ ๊ตฌํ ๊ฒํ
- ๋ก๊ทธ ์ ๋ต ๋ฐ ๋ก๊ทธ ํ์ผ ์ ์ฅ ์์น ๊ด๋ จ ์ค์
- Table DROP ๋ฐฉ์ง
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐ์ดํฐ ๋ณ ์ญ์ ์ ๋ต
- ์๋น์ค ์ฃผ์ ์ฟผ๋ฆฌ ์ฑ๋ฅ ์ ๊ฒ ๋ฐ ์ธ๋ฑ์ค ์ ๋ต
- APP ์๋ฒ / SSE ์๋ฒ ๋ถ๋ฆฌ ์ค๊ณ ๋ฌธ์
- Read Write DB ๋ถ๋ฆฌ
- ๋ถํํ ์คํธ ๊ณผ์ ๋ฐ ๊ฒฐ๊ณผ
- SSE์์ ํธ๋ฆฌ๊น ๋ฉ์์ง๋ฅผ ์ ๋ฌํ๊ณ GET์ ํ๋ ๊ตฌ์กฐ๋ฅผ ๋ณ๊ฒฝ
- ํ ์คํธ ํ๊ฒฝ์์ ๋ฐ์ํ DB ์ปค๋ฅ์ ๊ณ ๊ฐ ์ด์ ์ ๋ฆฌ
- CI/CD - Docker Hub ์ฅ์ ๋์ ๋ฐ GHCR Fallback