Skip to content

SageHabitus/PagingComposeMVIApp

Repository files navigation

๐Ÿ“š ์ฑ… ๊ฒ€์ƒ‰ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (Jetpack Compose with Advanced MVI Concepts)

๋ณธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๊ฐ€ ๋งŽ์ง€๋งŒ ์˜๋„ํ•œ ๊ฒƒ์ž„์„ ์•Œ๋ ค๋“œ๋ฆฝ๋‹ˆ๋‹ค. Paging 3์™€ Room, Retrofit ๋“ฑ์˜ ์ตœ์‹  ์•ˆ๋“œ๋กœ์ด๋“œ ๊ธฐ์ˆ  ์Šคํƒ์„ ํ™œ์šฉํ•˜์—ฌ ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์™€ ์˜คํ”„๋ผ์ธ ์ €์žฅ์†Œ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋ณธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํšจ์œจ์ ์ด๊ณ  ์ผ๊ด€๋œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋˜์—ˆ๊ณ  ์ตœ๋Œ€ํ•œ ๊ตฌ๊ธ€์•„ํ‚คํ…์ณ๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋…ธ๋ ฅํ–ˆ์Šต๋‹ˆ๋‹ค.

์ƒ๋žตยท๋ณ‘ํ•ฉ ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„

DataSource, UseCase ์ƒ๋žต๊ฐ€๋Šฅ, DomainModel, PresentationModel ์ƒ๋žต ๊ฐ€๋Šฅ, UiState ๋ณ‘ํ•ฉ ๊ฐ€๋Šฅ

์ฃผ์š” ๊ธฐ๋Šฅ โœจ

  • ์‹ค์‹œ๊ฐ„ API ํ†ต์‹  ๐ŸŒ: ์นด์นด์˜คํ†ก API ๋˜๋Š” ๋„ค์ด๋ฒ„ API๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์‚ฌ์šฉ์ž ์š”์ฒญ์— ๋”ฐ๋ผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฑ… ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. Retrofit๊ณผ OkHttp๋ฅผ ํ†ตํ•ด ์•ˆ์ •์ ์ธ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

  • MVI ์•„ํ‚คํ…์ฒ˜ ๐Ÿง : MVI ํŒจํ„ด์„ ํ†ตํ•ด ๋ชจ๋“  ์‚ฌ์šฉ์ž ์ด๋ฒคํŠธ(Intent)๋ฅผ ์ค‘์•™์—์„œ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ UI ์ƒํƒœ(State)๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด UI์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. MVI์˜ ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ์ธ PartialStateChange, Intent, Event, State๋Š” ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๊ณ , ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์˜ ๋ช…ํ™•ํ•œ ํ๋ฆ„์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

  • ๋กœ์ปฌ ๋ฐ์ดํ„ฐ ์บ์‹ฑ ๐Ÿ : Room์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒ€์ƒ‰๋œ ์ฑ… ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ์ปฌ์— ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ์ด ๋Š๊ฒจ๋„ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํšจ์œจ์ ์ธ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ ๐Ÿ”„: Paging 3 ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ†ตํ•ด ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ํŽ˜์ด์ง€ ๋‹จ์œ„๋กœ ๋กœ๋“œํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค. Jetpack Compose์™€ ํ†ตํ•ฉ๋˜์–ด ๋ฌดํ•œ ์Šคํฌ๋กค, ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  • Jetpack Compose UI ๐Ÿ“ฑ: ์„ ์–ธ์  UI ํ”„๋ ˆ์ž„์›Œํฌ์ธ Jetpack Compose๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Android UI๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ด๊ณ , UI ์—…๋ฐ์ดํŠธ์˜ ๋ณต์žก์„ฑ์„ ์ค„์ž…๋‹ˆ๋‹ค.

  • CI ์ž๋™ํ™” ๐Ÿš€: GitHub Actions๋ฅผ ํ™œ์šฉํ•ด Pull Request ์‹œ ์ž๋™ ๋นŒ๋“œ ๋ฐ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ์ˆ  ์Šคํƒ ๐Ÿ› ๏ธ

  • ์–ธ์–ด: Kotlin
  • UI: Jetpack Compose
  • ๋„คํŠธ์›Œํฌ ํ†ต์‹ : Retrofit, OkHttp, Corouitne, Flow
  • ๋น„๋™๊ธฐ ์ž‘์—…: Corouitne, Flow, StateFlow, SharedFlow
  • JSON ํŒŒ์‹ฑ: Kotlin Serialization, Parcelable
  • ๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: Room
  • ์ด๋ฏธ์ง€ ๋กœ๋”ฉ: Coil
  • ์˜์กด์„ฑ ์ฃผ์ž…: Hilt
  • ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ: Paging 3 (+RemoteMediator)
  • ํ…Œ์ŠคํŠธ: JUnit, Espresso, Mokito, Compose UI Test
  • CI: GitHub Actions

MVI ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ์  ๋ฐ MVVM์˜ ํ•œ๊ณ„ ๐Ÿš€

MVVM์˜ ํ•œ๊ณ„

MVVM (Model-View-ViewModel) ์•„ํ‚คํ…์ฒ˜๋Š” Android ๊ฐœ๋ฐœ์—์„œ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ํŒจํ„ด์ด์ง€๋งŒ, ๋ช‡ ๊ฐ€์ง€ ํ•œ๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค:

  • ์ƒํƒœ ๊ด€๋ฆฌ์˜ ๋ณต์žก์„ฑ: ViewModel์ด ๋„ˆ๋ฌด ๋งŽ์€ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋˜๋ฉด์„œ ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • UI์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ๊ฐ•ํ•œ ๊ฒฐํ•ฉ: View์™€ ViewModel ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ์ด ๋ณต์žกํ•ด์ง€๋ฉด, ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์˜ ์–ด๋ ค์›€: ํŠนํžˆ ๋น„๋™๊ธฐ ์ž‘์—…์„ ๋‹ค๋ฃฐ ๋•Œ, ์ƒํƒœ ์ „์ด๋ฅผ ์ผ๊ด€์„ฑ ์žˆ๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ๊ฐ€ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.

MVI์˜ ์žฅ์ 

MVI๋Š” ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์žฅ์ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค:

  • ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„: ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ Intent โ†’ State โ†’ UI๋กœ ํ๋ฅด๋ฏ€๋กœ ๋””๋ฒ„๊น…์ด ์šฉ์ดํ•ฉ๋‹ˆ๋‹ค.
  • ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ์ƒํƒœ ๊ด€๋ฆฌ: ๋ชจ๋“  ์ƒํƒœ ๋ณ€๊ฒฝ์ด ๋ช…์‹œ์ ์ธ Intent์— ์˜ํ•ด ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์ค‘๊ฐ„ ์ƒํƒœ(PartialStateChange)๋ฅผ ํ†ตํ•ด ์ƒํƒœ ๋ณ€ํ™”์˜ ์„ธ๋ถ€ ์‚ฌํ•ญ์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ณ ๋ฆฝ๋œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง: ์ƒํƒœ ๊ด€๋ฆฌ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋ถ„๋ฆฌ๋˜๋ฏ€๋กœ ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง‘๋‹ˆ๋‹ค.
  • ์˜๋„ ๊ธฐ๋ฐ˜ ์ƒํ˜ธ์ž‘์šฉ: ์‚ฌ์šฉ์ž์˜ ์•ก์…˜(Intent)์— ๊ธฐ๋ฐ˜ํ•˜์—ฌ UI๋ฅผ ๊ฐฑ์‹ ํ•˜๋ฏ€๋กœ, UI์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๐Ÿ—‚๏ธ

์ด ํ”„๋กœ์ ํŠธ๋Š” Google Architecture ์›์น™์„ ์ค€์ˆ˜ํ•˜๋ฉฐ, core, data, domain, presentation์˜ ๋„ค ๊ฐ€์ง€ ๋ชจ๋“ˆ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

๋ชจ๋“ˆ๋ณ„ ์„ค๋ช…

  • core: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์—ญ ์„ค์ • ๋ฐ ์ดˆ๊ธฐํ™”๋ฅผ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ComposeMVIApplication.kt ํŒŒ์ผ์€ Hilt๋ฅผ ์‚ฌ์šฉํ•œ ์˜์กด์„ฑ ์ฃผ์ž…์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

  • data: ๋ฐ์ดํ„ฐ ๋ ˆ์ด์–ด๋กœ, API ํ˜ธ์ถœ, ๋กœ์ปฌ DB ๊ด€๋ฆฌ, ๋ฐ์ดํ„ฐ ๋งคํ•‘ ๋ฐ ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. Repository ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์—ฌ Domain ๋ ˆ์ด์–ด์™€ ํ†ต์‹ ํ•ฉ๋‹ˆ๋‹ค.

  • domain: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํฌํ•จํ•˜๋ฉฐ, Use Case๋ฅผ ํ†ตํ•ด Presentation ๋ ˆ์ด์–ด์— ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์˜ ๋ณต์žก์„ฑ์„ ์ˆจ๊ธฐ๊ณ , ๋‹จ์ˆœํ™”๋œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

  • presentation: UI๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉฐ, Jetpack Compose๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. MVI ํŒจํ„ด์„ ๊ตฌํ˜„ํ•˜์—ฌ UI ์ƒํƒœ์™€ ์‚ฌ์šฉ์ž ์ด๋ฒคํŠธ ๊ฐ„์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

CI ๋ฐ ํ…Œ์ŠคํŠธ ๐Ÿงช

  • GitHub Actions: PR ์ƒ์„ฑ ์‹œ ์ž๋™ ๋นŒ๋“œ ๋ฐ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•˜์—ฌ ์ฝ”๋“œ ํ’ˆ์งˆ์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • Compose UI Test: Jetpack Compose๋กœ ์ž‘์„ฑ๋œ UI์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • JUnit ๋ฐ Espresso: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ฐ UI ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค.