Skip to content

Latest commit

ย 

History

History
90 lines (66 loc) ยท 6.23 KB

File metadata and controls

90 lines (66 loc) ยท 6.23 KB

ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜ ์ ์šฉ ๋ฐฉ์‹

Orbit ๋ฉ€ํ‹ฐ ๋ชจ๋“ˆ ๊ณ„์ธต ๋‹ค์ด์–ด๊ทธ๋žจ

๊ณ„์ธต ์ฑ…์ž„ ์ •๋ฆฌ

Layer ๋Œ€ํ‘œ ๋ชจ๋“ˆ ํ•ต์‹ฌ ์ฑ…์ž„ ์ƒ์œ„ ๊ณ„์ธต ๋…ธ์ถœ ๋ฐฉ์‹
Domain domain ๋น„์ฆˆ๋‹ˆ์Šค ๋ชจ๋ธ, Repository ์ธํ„ฐํŽ˜์ด์Šค, Scheduler ๊ณ„์•ฝ Kotlin interface + data class (์˜์กด์„ฑ ์—ญ์ „)
Data data FortuneRepositoryImpl, AlarmRepositoryImpl ๋“ฑ ์™ธ๋ถ€ ์†Œ์Šค ์—ฐ๋™ Domain ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์ฒด๋ฅผ Hilt๋กœ ๋ฐ”์ธ๋”ฉ
UI/Feature feature:*, app Compose ํ™”๋ฉด, Orbit Container ๊ธฐ๋ฐ˜ ViewModel, ๋‚ด๋น„๊ฒŒ์ด์…˜ Domain ์ธํ„ฐํŽ˜์ด์Šค ์ฃผ์ž…, core ์œ ํ‹ธ ์‚ฌ์šฉ
Core core:* ๋„คํŠธ์›Œํฌ, ๋””์ž์ธ ์‹œ์Šคํ…œ, ์•Œ๋žŒ, ๋ฏธ๋””์–ด ๋“ฑ ์ธํ”„๋ผ์„ฑ ๋กœ์ง ํ•„์š”ํ•œ ๋ชจ๋“ˆ์— implementation์œผ๋กœ ์˜์กด

์ธํ„ฐํŽ˜์ด์Šค ์šฐ์„  ์ ‘๊ทผ

๋„๋ฉ”์ธ ๊ณ„์•ฝ(domain/src/main/java/com/yapp/domain/repository/FortuneRepository.kt)์€ ์–ด๋– ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์—๋„ ์˜์กดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

interface FortuneRepository {
    val fortuneCreateStatusFlow: Flow<FortuneCreateStatus>
    suspend fun getFortune(fortuneId: Long): Result<Fortune>
    suspend fun markFortuneSeen()
    // ...
}

UI ๊ณ„์ธต์—์„œ๋Š” ๊ตฌํ˜„์ฒด๊ฐ€ ์•„๋‹Œ ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์‹œ๋Š” feature/mission/src/main/java/com/yapp/mission/MissionViewModel.kt์ž…๋‹ˆ๋‹ค.

@HiltViewModel
class MissionViewModel @Inject constructor(
    private val fortuneRepository: FortuneRepository,
    // ์ƒ๋žต
) : ViewModel(), ContainerHost<MissionContract.State, MissionContract.SideEffect> {
    private fun completeMission(type: String) = intent {
        val status = fortuneRepository.fortuneCreateStatusFlow.first()
        // ์ƒํƒœ ๊ณ„์‚ฐ & ์‚ฌ์ด๋“œ์ดํŽ™ํŠธ ์ „ํŒŒ
    }
}

FortuneRepositoryImpl(data ๋ชจ๋“ˆ)์€ Retrofit/Room/Datastore๋ฅผ ์กฐํ•ฉํ•ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋งŒ์กฑ์‹œํ‚ค๊ณ , app ๋ชจ๋“ˆ์˜ Hilt module์—์„œ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค. ์ด ํ๋ฆ„ ๋•๋ถ„์— API ์ŠคํŽ™์ด ๋ฐ”๋€Œ์–ด๋„ UI ๋ชจ๋“ˆ์€ ์ธํ„ฐํŽ˜์ด์Šค ๊ทธ๋Œ€๋กœ ์œ ์ง€๋˜๋ฉฐ, ํ…Œ์ŠคํŠธ์—์„œ๋Š” Fake/Stub Repository๋งŒ ์ฃผ์ž…ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

MVVM vs. MVI, ๊ทธ๋ฆฌ๊ณ  MVI๋ฅผ ์‚ฌ์šฉํ•œ ์ด์œ 

์•ˆ๋“œ๋กœ์ด๋“œ์—์„œ ํ”ํžˆ MVVM๊ณผ MVI์˜ ์ฐจ์ด๋ฅผ ์–‘๋ฐฉํ–ฅ vs. ๋‹จ๋ฐฉํ–ฅ, ์—ฌ๋Ÿฌ ์ƒํƒœ vs. ๋‹จ์ผ ์ƒํƒœ๋กœ ์„ค๋ช…ํ•˜์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์ด ๊ตฌ๋ถ„์ด ์ •ํ™•ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๊ณต์‹ ๋ฌธ์„œ์˜ ์•„ํ‚คํ…์ฒ˜ ๋‹ค์ด์–ด๊ทธ๋žจ์„ ๋ณด๋ฉด MVVM ์—ญ์‹œ ์‚ฌ์šฉ์ž ์ด๋ฒคํŠธ โ†’ ViewModel โ†’ Data Layer โ†’ State โ†’ View ์˜ ๋‹จ๋ฐฉํ–ฅ(UDF) ํ๋ฆ„์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
๋˜ํ•œ ๋‹จ์ผ ์ƒํƒœ ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ MVI๋งŒ์˜ ํŠน์ง•์ด ์•„๋‹ˆ๋ผ MVVM์—์„œ๋„ ์ถฉ๋ถ„ํžˆ ์ ์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

์ œ๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ๋‘ ํŒจํ„ด์˜ ๋ณธ์งˆ์ ์ธ ์ฐจ์ด๋Š” โ€œ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์–ด๋–ป๊ฒŒ ๋ฐœ์ƒ์‹œํ‚ค๋Š”๊ฐ€โ€, ์ฆ‰ View์™€ ViewModel์˜ ๊ฒฐํ•ฉ๋„์ž…๋‹ˆ๋‹ค.

  • MVVM: View๊ฐ€ ViewModel์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด View๋Š” ViewModel์˜ API์— ์˜์กดํ•˜๊ณ , ์ƒํƒœ ๋ณ€๊ฒฝ ๋กœ์ง์ด ์—ฌ๋Ÿฌ ๋ฉ”์„œ๋“œ์— ํฉ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ƒํƒœ๊ฐ€ ์—ฌ๋Ÿฌ ์†Œ์Šค์— ๋ถ„์‚ฐ๋˜๋ฉด ํ…Œ์ŠคํŠธ ์‹œ ๊ฐœ๋ณ„ ํ๋ฆ„์„ ๋”ฐ๋กœ ๊ฒ€์ฆํ•ด์•ผ ํ•˜๊ณ  UI๊ฐ€ ์ผ์‹œ์ ์œผ๋กœ ๋ถˆ์™„์ „ํ•œ ์Šค๋ƒ…์ƒท์„ ๋ณด์—ฌ์ค„ ๊ฐ€๋Šฅ์„ฑ๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • MVI: View๋Š” Intent(์‚ฌ์šฉ์ž์˜ ์˜๋„)๋งŒ ๋ฐœํ–‰ํ•˜๊ณ , ์ƒํƒœ ๋ณ€๊ฒฝ์€ ViewModel ๋‚ด๋ถ€์˜ Reducer์—์„œ ๋‹จ์ผ ๊ฒฝ๋กœ๋กœ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์ƒํƒœ ๋ณ€ํ™” ํ๋ฆ„์ด ํ‘œ์ค€ํ™”๋˜๊ณ  ์ˆœ์ฐจ์ ์ด๊ธฐ ๋•Œ๋ฌธ์— UI ์ผ๊ด€์„ฑ๊ณผ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.

Orbit-MVI๋Š” ์ด๋Ÿฌํ•œ MVI ์ฒ ํ•™์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„ ViewModel ๊ธฐ๋ฐ˜ ๊ฐœ๋ฐœ์˜ ์ƒ์‚ฐ์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅด๋น„ ํ”„๋กœ์ ํŠธ์—์„œ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.

MVVM(UDF) vs. Orbit-MVI ๋น„๊ต

ํ•ญ๋ชฉ MVVM(UDF) ํ•ด์„ Orbit-MVI ์ ์šฉ
๋ฐ์ดํ„ฐ ํ๋ฆ„ View โ†’ ViewModel ๋ฉ”์„œ๋“œ โ†’ Flow/State โ†’ View. Compose์˜ collectAsState()๋กœ ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์œ ์ง€ ๊ฐ€๋Šฅ. View โ†’ processAction() โ†’ Intent โ†’ Reducer โ†’ State/SideEffect. Orbit DSL์ด ์ˆœ์ฐจ ์ฒ˜๋ฆฌ์™€ ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ์„ ๋ณด์žฅ.
์ƒํƒœ ๊ด€๋ฆฌ ์—ฌ๋Ÿฌ StateFlow/mutableStateOf๋ฅผ ๋ณ„๋„๋กœ ๊ด€๋ฆฌํ•˜๋ฉด ์ƒํƒœ ์กฐ๊ฐ์ด ํฉ์–ด์งˆ ์ˆ˜ ์žˆ์Œ. ํ™”๋ฉด ์ƒํƒœ๋ฅผ ๋‹จ์ผ Contract.State ๊ฐ์ฒด๋กœ ํ‘œํ˜„ (MissionContract.State, FortuneContract.State ๋“ฑ).
์˜์กด ๊ด€๊ณ„ View๊ฐ€ ViewModel์˜ public ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœ โ†’ ๊ฒฐํ•ฉ๋„ ์ฆ๊ฐ€. View๋Š” Intent(Action)๋งŒ ์ „๋‹ฌ. ์ƒํƒœ ๋ณ€๊ฒฝ ๋กœ์ง์€ Reducer ๋‚ด๋ถ€๋กœ ์บก์Аํ™”๋˜์–ด ๊ฒฐํ•ฉ๋„ ๊ฐ์†Œ.
ํ…Œ์ŠคํŠธ ์—ฌ๋Ÿฌ Flowยท์ƒํƒœ ์†Œ์Šค๋ฅผ ๊ฐ๊ฐ ๊ฒ€์ฆํ•ด์•ผ ํ•จ. Intent ํ˜ธ์ถœ โ†’ ๋‹จ์ผ State snapshot๋งŒ ๊ฒ€์ฆ. Orbit test ๋ชจ๋“ˆ๋กœ ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐ๋ฐ˜ ํ…Œ์ŠคํŠธ ์šฉ์ด.
์ ์šฉ ๋ชจ๋“ˆ core/ui ๋‚ด๋ถ€ ๊ณต์šฉ UI, Compose Preview ๋“ฑ์—์„œ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ MVVM ๊ธฐ๋ฐ˜. feature/mission, feature/fortune, feature/home, feature/onboarding ๋“ฑ์—์„œ Orbit-MVI ์ ์šฉ.

Orbit-MVI๋ฅผ ์„ ํƒํ•œ ์ด์œ ์™€ ์‹ค์ œ ์žฅ์ 

์ด์ „์— ๋…ผํ”„๋ ˆ์ž„์›Œํฌ MVI๋ฅผ ๊ตฌํ˜„ํ–ˆ๋˜ ๊ฒฝํ—˜์ด ์žˆ์–ด ์ƒ์šฉ MVI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๋น„๊ตํ•˜๋ฉฐ ์žฅ์ ์„ ๋А๋‚„ ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

1) Mavericks ๋Œ€๋น„ ์‹ค์งˆ์  ์žฅ์ 

  • SavedStateHandle์„ ์ง์ ‘ ์ฃผ์ž…ํ•  ์ˆ˜ ์—†์Œ โ†’ ์ƒํƒœ ๋ณต์› ์‹œ @PersistState ์˜์กด
  • ViewModel ์ƒ์„ฑ ์‹œ AssistedInject + ViewModelModule ํ•„์š” โ†’ ๋ณด์ผ๋Ÿฌํ”Œ๋ ˆ์ดํŠธ ์ฆ๊ฐ€

2) Orbit-MVI ์ฑ„ํƒ ์ด์œ 

  • DSL ๊ธฐ๋ฐ˜ ์„ ์–ธ์  ์ƒํƒœ ๊ด€๋ฆฌ

    • intent {}, reduce {}, postSideEffect {} ํ˜•ํƒœ๋กœ ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•
    • ์ƒํƒœ ๋ณ€๊ฒฝ ๊ฒฝ๋กœ๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋‹จ์ผํ™” โ†’ ๊ฐ€๋…์„ฑ ์šฐ์ˆ˜
  • ์ƒ๋ช…์ฃผ๊ธฐ ์•ˆ์ „ ํ”Œ๋กœ์šฐ ์ฒ˜๋ฆฌ

    • repeatOnSubscription์œผ๋กœ ํ™”๋ฉด ๊ตฌ๋… ์‹œ์ ์—๋งŒ Flow ์ˆ˜์ง‘
    • ์ƒ๋ช…์ฃผ๊ธฐ ์ง์ ‘ ์ œ์–ด ๋ถˆํ•„์š”
  • ๋‚ด๋ถ€ DispatchQueue ๊ธฐ๋ฐ˜ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ

    • ๋ชจ๋“  Intent๊ฐ€ ํ์— ์Œ“์—ฌ ์ฐจ๋ก€๋Œ€๋กœ ์‹คํ–‰
    • ๊ฒฝ์Ÿ ์ƒํƒœ๋‚˜ ์ค‘๊ฐ„ ์Šค๋ƒ…์ƒท ๋ฌธ์ œ๋ฅผ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ˆ˜์ค€์—์„œ ํ•ด๊ฒฐ
    • ๊ฐœ๋ฐœ์ž๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ์ง‘์ค‘ ๊ฐ€๋Šฅ

orbit_dispatch_queue.png