๊ฐœ๋ฐœ ์ผ์ง€ ๐Ÿ’ป/Swemo

Swemo - Iteration 3: ๋ฉ”๋ชจ ํ™”๋ฉด์˜ State & Event ์ฒด๊ณ„ ์ •๋ฆฌ

interfacer_han 2026. 2. 8. 20:01

#1 ๊ฐœ๋ฐœ ๋ชฉํ‘œ (Iteration Goal)

#1-1 ์ด์ƒ(Ideal)๊ณผ ์ง•๊ฒ€๋‹ค๋ฆฌ

์ด์ƒ(Ideal)

์‚ฌ์šฉ์ž ๊ด€์ ์—์„œ ์ง๊ด€์ ์ธ ๋ฉ”๋ชจ ์•ฑ์„ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค. ์‚ฌ์šฉ์ž์˜ ์˜๋„์— ๊ฑธ๋งž์€ ํ–‰๋™์„ ์œ ๋„ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„๊ฐ€ ๋“œ๋Ÿฌ๋‚˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ์–ด์•ผ ํ•œ๋‹ค.

 

Iteration 3: ๋ฉ”๋ชจ ํ™”๋ฉด์˜ State & Event ์ฒด๊ณ„ ์ •๋ฆฌ

์ง€๊ธˆ๊นŒ์ง€ ๊ตฌํ˜„ํ•œ UI๋Š” ์ผ์ข…์˜ ๋ผˆ๋Œ€์— ๋ถˆ๊ณผํ–ˆ๋‹ค. ์ด ๋ผˆ๋Œ€์— ๋ถ™์ผ๋งŒํ•œ '์‚ด'์ด๋ž€, ์‚ฌ์šฉ์ž ํ–‰๋™์— ์˜ํ•œ ์ƒํ˜ธ์ž‘์šฉ(Event) ๊ทธ๋ฆฌ๊ณ  ์ƒํƒœ(State)์˜ ๋ณ€ํ™”๋‹ค. ์•ฑ์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ œ๊ณตํ•  '์ง๊ด€์ ์ธ ํ–‰๋™'์ด ๋ฌด์—‡์ธ์ง€๋ฅผ ์ •์˜ํ•˜๊ณ , (์ข‹์€ ๋ฐฉํ–ฅ์œผ๋กœ) ์—…๋ฐ์ดํŠธ ํ•ด๋‚˜๊ฐ€์•ผ ํ•œ๋‹ค. 

 

#1-2 ๊ฐœ๋ฐœ ๋ฒ”์œ„

์ด๋ฒˆ ๊ฐœ๋ฐœ ๋ฒ”์œ„ (In Scope)

  1. State down / Event up ๊ตฌ์กฐ ํ™•๋ฆฝ
    • Screen์€ ViewModel์„ ๋ชฐ๋ผ์•ผ ํ•จ
    • ViewModel์€ Compose State๋ฅผ ๋ชฐ๋ผ์•ผ ํ•จ
  2. ViewModel ๊ตฌํ˜„
    • ViewModel ๋ณด์œ ํ•  State์™€ UI ๋‹จ์—์„œ ๋ณด์œ ํ•  State (DrawerState ๋“ฑ)๋ฅผ ๊ตฌ๋ถ„
    • ์ด๋ฒคํŠธ ๋ฐœ์ƒ์— ๋”ฐ๋ฅธ State ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•œ onEvent() ํ•จ์ˆ˜ ๋ณด์œ 
      • ์ „์ฒด ์ฝ”๋“œ์˜ ํ‰๊ท  ๊ฐ€๋…์„ฑ์ด ์ €ํ•ด๋˜๊ธฐ์— ์ง„ํ–‰ํ•˜์ง€ ์•Š์Œ (์ฐธ์กฐ: #2-2)
  3. MemoUiState ํด๋ž˜์Šค ๊ตฌํ˜„
    • ViewModel์—์„œ ๊ด€๋ฆฌํ•  ๋ชจ๋“  State๋ฅผ Wrapping ํ•œ ํด๋ž˜์Šค
  4. MemoEvent ํด๋ž˜์Šค ๊ตฌํ˜„
    • Event sealed interface
    • ์ „์ฒด ์ฝ”๋“œ์˜ ํ‰๊ท  ๊ฐ€๋…์„ฑ์ด ์ €ํ•ด๋˜๊ธฐ์— ์ง„ํ–‰ํ•˜์ง€ ์•Š์Œ (์ฐธ์กฐ: #2-2)
  5. MemoScreen ์—…๋ฐ์ดํŠธ
    • ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ State ๋ฐ Event๋งŒ ๋ฐ›๋„๋ก ์—…๋ฐ์ดํŠธ

 

๋ฒ”์œ„ ์™ธ (Out of Scope)

  1. Data layer ๊ด€๋ จ
  2. IME, focus, scroll ๊ด€๋ จ
  3. UI ๋‹ค๋“ฌ๊ธฐ
    • ์• ๋‹ˆ๋ฉ”์ด์…˜
    • ์„ธ๋ถ€ ๋ ˆ์ด์•„์›ƒ ๋ฏธ์„ธ์กฐ์ •
    • ๋””์ž์ธ ๊ฐœ์„ 
  4. ์„ฑ๋Šฅ(์ตœ์ ํ™”) ๊ด€๋ จ
    • derivedStateOf ์ตœ์ ํ™”
    • recomposition ๋ถ„์„
    • snapshotFlow ์‚ฌ์šฉ

 

#2 ๋ณ€๊ฒฝ ๋‚ด์—ญ (์ปค๋ฐ‹ ๋ชฉ๋ก)

#2-1 Iteration 3-1: ViewModel ๋„์ž… ๋ฐ State ์ •๋ฆฌ

Swemo์˜ MemoScreen์—๋Š” MemoEditor๊ฐ€ ์กด์žฌํ•˜๊ณ , ์ด ์ปดํฌ๋„ŒํŠธ์—์„œ (์•ž์œผ๋กœ์˜ ๊ตฌํ˜„์—์„œ) ๊ฝค ๋ณต์žกํ•œ State ๋ฐ Event๊ฐ€ ์กด์žฌํ•  ํ„ฐ๋‹ค.

data class MemoUiState(
    val categories: List<Category> = emptyList(),
    val selectedCategory: Category? = null,
    val memos: List<Memo> = emptyList(),
    val allLabels: Set<String> = emptySet(),
    val editorState: EditorState = EditorState()
) {
    data class EditorState(
        val isVisible: Boolean = false,
        val editingMemo: Memo? = null
    )
}

๊ทธ๋ž˜์„œ, MemoUiState ์†์— inner class๋กœ EditorState๋ฅผ (๋ณ„๋„๋กœ ๋ถ„๋ฆฌํ•ด) ์„ ์–ธํ–ˆ๋‹ค.

 

์˜์กด์„ฑ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด Hilt ๋“ฑ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์ถ”๊ฐ€ํ–ˆ๊ณ , ๋งˆ๋ฌด๋ฆฌ๋กœ ์ด ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ†ตํ•ด์„œ Now in Android์™€ ๋ฒ„์ „ ์นดํƒˆ๋กœ๊ทธ์—์„œ์˜ ํ”„๋กœํ„ฐํ”ผ๋ช…์„ ๋งž์ถฐ์ฃผ์—ˆ๋‹ค.

 

๊ด€๋ จ ๊ฐœ๋ฐœ ๋ฒ”์œ„

  • State down / Event up ๊ตฌ์กฐ ํ™•๋ฆฝ
  • ViewModel ๊ตฌํ˜„
  • MemoUiState ํด๋ž˜์Šค ๊ตฌํ˜„

 

์ฃผ์š” ๋ณ€๊ฒฝ์ 

  • MemoViewModel ๋„์ž… (์˜์กด์„ฑ ์ฃผ์ž…์€ Hilt๋ฅผ ์ด์šฉ)
  • MemoUiState๋กœ MemoScreen์˜ ๋ชจ๋“  State๋ฅผ ๊ฐ์ฒด ํ•˜๋‚˜๋กœ ํ†ตํ•ฉ(Wrapping)

 

์–ป์€ ๊ฒฝํ—˜์น˜

๋”๋ณด๊ธฐ
  • MemoScreen ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋“ค์–ด๊ฐˆ ๋žŒ๋‹ค ํ•จ์ˆ˜๋ฅผ ํ•จ์ˆ˜ ์ฐธ์กฐ(Callable Reference)๋กœ ํ‘œ๊ธฐํ•ด ๋ดค๋‹ค. ๊ฐ€๋…์„ฑ ๋ฉด์—์„œ ํ›จ์”ฌ ๋‚˜์•˜๋‹ค.

 

์ฐธ๊ณ 

 

#2-2 Iteration 3-2: Event ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๊ตฌ์กฐ ์„ค๊ณ„

#2-1์— ๋งž์ถฐ, ์˜ˆ์ „์— ๋ฐฐ์šด ๋ฐฉ์‹๋Œ€๋กœ MemoEvent๋ฅผ ๋งŒ๋“ค์–ด์„œ Event ๊ด€๋ฆฌ๋ฅผ ํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค.

// ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ–ˆ๋˜ ์ฝ”๋“œ

sealed interface MemoEvent {
    data class SelectCategory(val category: Category) : MemoEvent
    data class AddMemo(val memo: Memo?) : MemoEvent
    data class ChangeAddCategoryDialogVisibility(val visible: Boolean) : MemoEvent

    sealed interface MemoEditor : MemoEvent {
        data object ToggleVisibility : MemoEditor
    }
}

์ด๋ ‡๊ฒŒ Event๋ฅผ sealed interface๋กœ ๋ฌถ๋Š” ๊ฒƒ์ด ๋ณด๊ธฐ์—๋Š” ํ†ตํ•ฉ์„ฑ ์žˆ์–ด ๋ณด์ธ๋‹ค. ์ดํ›„ MemoViewModel์— fun onEvent(event: MemoEvent)๋ฅผ ์„ ์–ธํ•ด์„œ MemoScreen์œผ๋กœ๋ถ€ํ„ฐ Event๋ฅผ ๋ฐ›๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๊พธ๋ ค ํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด ๋ณด๋‹ˆ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

// in MemoScreen.kt

ModalNavigationDrawer(
    ...
    drawerContent = {
        CategorySelector(
            ...
            onAddCategoryButtonClick = {
//              onAddCategoryDialogVisibilityChange(true)
                onEvent(MemoEvent.ChangeAddCategoryDialogVisibility(true)) // ๋ณต์žกํ•ด์กŒ๋‹ค
            }
        )
    }
)

์ฃผ์„ ์ฒ˜๋ฆฌ๋œ ์ฝ”๋“œ๊ฐ€ ์›๋ž˜ ์ฝ”๋“œ์ธ๋ฐ, ๊ทธ ์•„๋ž˜ ์ฝ”๋“œ์™€ ๊ฐ™์ด ๊ฐ€๋…์„ฑ์ด ๋‚˜์œ ์ฝ”๋“œ๋กœ ๋ฐ”๊ฟ”์ฃผ์–ด์•ผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋”ฐ๋ผ์„œ, ๋‚˜์ค‘์— Event๊ฐ€ ์ •๋ง ๋งŽ์ด ๋ณต์žกํ•ด์ ธ์„œ ์ง‘์ค‘ ๊ด€๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•  ํ•„์š”๊ฐ€ ์ƒ๊ธฐ๊ธฐ ์ „๊นŒ์ง€๋Š” (MemoEvent ๋“ฑ์œผ๋กœ) Event๋“ค์„ ๋ฌถ์ง€ ์•Š๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

๊ฒฐ๋ก ์ ์œผ๋กœ ์ด๋ฒˆ ์ปค๋ฐ‹์—์„ , ์•„์ง MemoScreen์— ๋‚จ์•„์žˆ๋˜ ์ด๋ฒคํŠธ ์„ ์–ธ๋ถ€๋“ค์„ ViewModel๋กœ ์˜ฌ๋ฆฌ๊ณ  ์ฝ”๋“œ๋ฅผ ์•ฝ๊ฐ„ ๋‹ค๋“ฌ๋Š” ์ •๋„๋กœ ๋งˆ๋ฌด๋ฆฌํ–ˆ๋‹ค.

 

๊ด€๋ จ ๊ฐœ๋ฐœ ๋ฒ”์œ„

  • State down / Event up ๊ตฌ์กฐ ํ™•๋ฆฝ

 

์ฃผ์š” ๋ณ€๊ฒฝ์ 

  • MemoScreen์— ๋‚จ์•„์žˆ๋˜ ์ด๋ฒคํŠธ ์„ ์–ธ๋ถ€๋“ค์„ ViewModel๋กœ ์˜ฌ๋ฆผ

 

์ฝ”๋“œ ์Šค๋‹ˆํŽซ - MemoScreen ํ˜ธ์ถœ๋ถ€

MemoScreen(
    uiState = uiState,
    drawerState = drawerState,
    // events
    onCategorySelected = viewModel::selectCategory,
    onAddMemoClick = { viewModel.addMemo() },
    onMemoEditorToggleButtonClick = viewModel::toggleEditorVisibility,
    onAddCategoryDialogVisibilityChange = viewModel::changeAddCategoryDialogVisibility,
)

Event๊ฐ€ ํ•˜๋‚˜ ์ถ”๊ฐ€๋  ๋•Œ๋งˆ๋‹ค ๋žŒ๋‹ค ํ•จ์ˆ˜ ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ํ•˜๋‚˜์”ฉ ๋Š˜์–ด๋‚  ๊ฒƒ์ด๋‹ค. MemoEvent ๋ฐ MemoViewModel.onEvent()๊ฐ€ ์žˆ์—ˆ๋‹ค๋ฉด, ๋ชจ๋“  ์ด๋ฒคํŠธ ๋žŒ๋‹ค ํ•จ์ˆ˜๋ฅผ onEvent = viewModel::onEvent ๋กœ ํ‰์น  ์ˆ˜ ์žˆ์—ˆ์„ ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Ÿฐ ์ด๋“๋ณด๋‹ค, ๋‹ค๋ฅธ ๋ถ€๋ถ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๊ฐ€๋…์„ฑ์ ์ธ ์†์‹ค์ด ๋” ํด ๊ฒƒ์ด๋‹ค.

 

์–ป์€ ๊ฒฝํ—˜์น˜

๋”๋ณด๊ธฐ

 

์ฐธ๊ณ 

 

#2-3 Iteration 3-3

์ถ”๊ฐ€ ์˜ˆ์ •.

 

#3 ํ…Œ์ŠคํŠธ

์ถ”๊ฐ€ ์˜ˆ์ •.

 

#4 ๊ฐœ๋ฐœ ๊ฒฐ๊ณผ (Iteration Outcome)

์ถ”๊ฐ€ ์˜ˆ์ •.