์น๊ตฌ๋ค๊ณผ ์์ฌ ๋ฐ ์ด๋์ ์นผ๋ก๋ฆฌ ์ ์ฝ ์์ด ๊ฐํธํ๊ฒ ๊ธฐ๋กํ๊ณ ๊ณต์ ํฉ๋๋ค. โ์์ ๋์งโ ๊ฐ๋ ์ ํ์ฉํ ์ํต๊ณผ ์ฑ๋ฆฐ์ง๋ฅผ ํตํด ๋ค์ด์ดํธ ์ต๊ด์ ์ง์ํ๋๋ก ๋์ต๋๋ค.
| ๋ถ๋ฅ | ์ฌ์ฉ ๊ธฐ์ |
|---|---|
| Language | Kotlin |
| UI | Jetpack Compose, Coil, Lottie |
| Architecture | Clean Architecture + MVI, ๋ฉํฐ๋ชจ๋ |
| DI | Hilt |
| Async | Coroutine / Flow |
| Network | Retrofit, OkHttp |
| Local | DataStore, EncryptedSharedPreferences(ํ ํฐ) |
| Navigation | Type-safe Navigation + NavigationHelper |
| Auth | Kakao SDK, Google Sign-In |
| Camera | CameraX (์ดฌ์ยทํฌ๋กญยทEXIF ์ ๊ทํ) |
| Health | Health Connect (๊ฑธ์ ์ ์ฝ๊ธฐ) |
| Firebase | Cloud Messaging, Crashlytics, Analytics, Remote Config |
| Test | JUnit (์์ ๋ก์ง ๋จ์ ํ ์คํธ), Konsist (์ํคํ ์ฒ ๊ท์น ๊ฒ์ฆ) |
MODY Android๋ ๋ฉํฐ๋ชจ๋ ๊ธฐ๋ฐ์ผ๋ก ๊ตฌ์ฑ๋์ด ์์ผ๋ฉฐ, Feature์ Core Layer๋ฅผ ๋ถ๋ฆฌํ์ฌ ์ ์ง๋ณด์์ฑ๊ณผ ํ์ฅ์ฑ์ ๋์์ต๋๋ค.
ํ๋ฉด์ ๋จ๋ฐฉํฅ์ผ๋ก ํ๋ฆ
๋๋ค. ์ฌ์ฉ์ ์ก์
์ Intent ๋ก๋ง ViewModel ์ ๋ค์ด๊ฐ๊ณ , ์ํ ๋ณ๊ฒฝ์
setState { copy(...) } ํ ๊ณณ์ผ๋ก ๋ชจ์
๋๋ค. Screen ์ ๋ ๋๋ง๋ง ๋งก์ ๋น์ฆ๋์ค ๋ก์งยทํ๋ฉด ์ด๋์
์ง์ ๋ค๋ฃจ์ง ์์ต๋๋ค.
Screen โโonIntent(Intent)โโ> ViewModel โโ> Repository(:core:domain ์ธํฐํ์ด์ค)
^ โ
โโโโโโโ StateFlow<State> โโโโโโ
mody
โโโ app # Application, Hilt EntryPoint
โโโ presentation # MainActivity, AppNavHost, ์ฑ ์ง์
๋ฐ ๋ผ์ฐํ
โโโ core
โ โโโ common-ui # MVI Base(BaseViewModel, UiState, UiIntent)
โ โโโ designsystem # Theme, Typography, Components
โ โโโ navigation # Route, NavigationHelper
โ โโโ domain # Model, Repository, UseCase
โ โโโ data # Repository ๊ตฌํ, DataStore
โ โโโ network # Retrofit, DTO, Interceptor, Authenticator
โ โโโ camera # CameraX ์ดฌ์/ํฌ๋กญ ์ค๋ฒ๋ ์ด, EXIF ์ ๊ทํ
โโโ feature
โโโ auth
โโโ onboarding
โโโ group
โโโ feed
โโโ challenge
โโโ record
โโโ notification
โโโ mypage
graph TD
app[":app<br/>Application ยท DI ์กฐ๋ฆฝ"]
pres[":presentation<br/>MainActivity ยท AppNavHost"]
feat[":feature:*<br/>auth ยท onboarding ยท group ยท feed<br/>challenge ยท record ยท notification ยท mypage"]
cui[":core:common-ui"]
ds[":core:designsystem"]
nav[":core:navigation"]
cam[":core:camera"]
dom[":core:domain<br/>์์กด์ฑ ์์"]
data[":core:data"]
net[":core:network"]
app --> pres
app --> data
app --> cui
app --> nav
app --> dom
pres --> feat
pres --> cui
pres --> ds
pres --> nav
pres --> dom
feat --> cui
feat --> ds
feat --> nav
feat --> dom
feat -. "challenge ยท record ๋ง" .-> cam
cam --> ds
cam -- "api" --> dom
cui --> dom
data --> dom
data --> net
net --> dom
classDef domain fill:#2d5a3d,stroke:#4caf50,color:#fff
classDef impl fill:#4a3a5a,stroke:#9575cd,color:#fff
class dom domain
class data,net impl
ํ์ดํ๋ ๊ธฐ๋ณธ์ด implementation ์
๋๋ค. api ๋ก ํ์ํ ๊ณณ์ ๊ณต๊ฐ ์๊ทธ๋์ฒ์ ๊ทธ ๋ชจ๋์
ํ์
์ด ๋๋ฌ๋ ์๋น์์๊ฒ ์ ์ด๋๋ ์์กด์ฑ์
๋๋ค.
์์กด์ฑ ๋ฐฉํฅ์ ๋ฌธ์๋ ์ฝ๋๋ฆฌ๋ทฐ๊ฐ ์๋๋ผ Gradle ๋ชจ๋ ๊ฒฝ๊ณ๋ก ๊ฐ์ ํฉ๋๋ค.
| ๊ท์น | ๊ฐ์ ๋ฐฉ์ |
|---|---|
:core:domain ์ ์๋ฌด๊ฒ๋ ์์กดํ์ง ์๋๋ค |
์์ Kotlin ๊ณ์ฝ ๊ณ์ธต (Android SDKยทRetrofit ๋ฏธ์ฐธ์กฐ) |
:feature:* ๋ :core:data / :core:network ๋ฅผ ๋ชจ๋ฅธ๋ค |
์์กด์ฑ์ ์ ์ธํ์ง ์์ โ DTOยทRetrofit API ์ฐธ์กฐ ์ ์ปดํ์ผ ์คํจ |
:feature:* ๋ผ๋ฆฌ ์๋ก ์์กดํ์ง ์๋๋ค |
ํ๋ฉด ์ด๋์ :core:navigation ์ Route + NavigationHelper ๊ฒฝ์ |
๊ตฌํ์ฒด ์ฃผ์
์ :app ํ ๊ณณ์์๋ง |
:core:data ๋ฅผ ์์กดํ๋ ์ ์ผํ ๋ชจ๋ |
๊ณต๊ฐ ์๊ทธ๋์ฒ์ ๋๋ฌ๋๋ ์์กด์ฑ๋ง api |
๋๋จธ์ง๋ implementation ์ผ๋ก ์ ์ด ์ฐจ๋จ โ ์๋น์๊ฐ ๋จ์ ๋ด๋ถ ํ์
์ ๊ธฐ๋์ง ๋ชปํจ |
๊ณต์ ๊ฐ ๋ฐ์ํ๋ ์๊ฐ core ๋ก ์น๊ฒฉํฉ๋๋ค. :core:camera ๋ :feature:challenge ์
:feature:record ๊ฐ ํจ๊ป ์ฐ๊ฒ ๋๋ฉด์, feature ๊ฐ ์์กด์ ๋ง๋ค์ง ์๊ธฐ ์ํด ๋ถ๋ฆฌํ ๋ชจ๋์
๋๋ค.
์น๊ฒฉํ ๋ชจ๋์ ๊ณต๊ฐ ํ๋ฉด์ ์ต์๋ก ์ ์งํฉ๋๋ค. :core:camera ๊ฐ ๋ฐ์ผ๋ก ์ฌ๋ ๊ฒ์
์ค๋ฒ๋ ์ด ์ง์
์ ModyCameraOverlay ์ ํ๋ ์ ๋น์จ ์์๋ฟ์ด๊ณ , ์ดฌ์ยท๋ณด์ ๋จ๊ณ์ ํ์ผ
์ ๋ฆฌ ํจ์๋ internal ์
๋๋ค. ๋จ๊ณ๋ฅผ ๊ฑด๋๋ด ๋ฐ์ชฝ ํธ์ถ์ด๋ ์ธ๋ถ์์์ ์บ์ ์ญ์ ๋ฅผ
์ปดํ์ผ ๋จ๊ณ์์ ๋ง์ต๋๋ค.
Gradle ์ ๋ชจ๋ ์ฌ์ด๋ง ๋ง์ต๋๋ค. ๊ฐ์ ๋ชจ๋ ์์์ State ์ var ๋ฅผ ๋ฃ๊ฑฐ๋ Screen ์ด
NavController ๋ฅผ ์ง์ ๋ง์ง๋ ๊ฒ์ ์ปดํ์ผ์ด ํต๊ณผํ๋ฏ๋ก, Konsist
๋ก ๊ฒ์ฌํด CI ์์ ์คํจ์ํต๋๋ค (app/src/test/.../ArchitectureRuleTest.kt, 10๊ฐ ๊ท์น).
@Test
fun `State ์ var ํ๋กํผํฐ๊ฐ ์๋ค`() { /* ... */ }
@Test
fun `Screen ์ NavController ๋ฅผ ์ง์ ๋ค๋ฃจ์ง ์๋๋ค`() { /* ... */ }| ๊ฒ์ฌ ๋์ | ๊ท์น |
|---|---|
| State / Intent | State ๋ data class : UiState, Intent ๋ sealed : UiIntent, State ์ var ๊ธ์ง |
| ViewModel | BaseViewModel ์์, @HiltViewModel ์ฃผ์
|
| Screen | NavController ์ง์ ์กฐ์ ๊ธ์ง, Repository ์ง์ ์ฐธ์กฐ ๊ธ์ง |
| ๋ ์ด์ด | featureยทpresentation ์ด data/network ๊ตฌํ์ฒด ๋ฏธ์ฐธ์กฐ, domain ์ด Android ํ๋ ์์ํฌ ๋ฏธ์์กด |
| ๋ค์ด๋ฐ | Repository ๊ตฌํ์ฒด๋ Impl ๋ก ๋๋๊ณ @Singleton |
./gradlew :app:testDebugUnitTest # ์ํคํ
์ฒ ๊ท์น ๊ฒ์ฌ ํฌํจUIยท๋คํธ์ํฌ์ ๋ฌถ์ด์ง ์๋ ๊ณ์ฐ์ ์์ ํจ์๋ก ๋นผ์ JVM ํ ์คํธ๋ก ๋ฎ์ต๋๋ค. ์ขํยท์๊ฐยท๊ฒฝ๊ณ๊ฐ์ ๊ธฐ๊ธฐ์์ ๋์ผ๋ก ํ์ธํ๊ธฐ ์ด๋ ค์ด ๋์ ํ ์คํธ๋ก๋ ์ธ๊ฒ ์กํ๋๋ค (36๊ฐ).
| ๋์ | ๋ฌด์์ ๊ณ ์ ํ๋ |
|---|---|
:core:camera CropGeometryTest |
ํฌ๋กญ ํ๋ ์ ์์น, ๋๋๊ทธ ๊ฒฝ๊ณ ํด๋จํ, ์ธ๋ก ์ฌ๋ผ์ด์ค ํฌ๋กญ ์์ญ |
:core:domain StepSyncWindowTest |
๊ฑธ์ ์ ๋๊ธฐํ ๊ตฌ๊ฐ ๋ถํ โ ๋ ์ง ๊ฒฝ๊ณ, ์์ ์ฒ๋ฆฌ, ์ฝ๊ธฐ ์ฐฝ ์ด๊ณผ |
:core:domain StepChallengeStatusTest |
์ฑ๋ฆฐ์ง ์ํ ๋งคํ, ๋ฌ์ฑ๋ฅ ๋ด๋ฆผยท์ํยท0 ๋๋์ |
:feature:challenge StepChallengeTitleTest |
์ฑ๋ฆฐ์ง ์ ๋ชฉ ํ์ฑ์ ๋น์ ์ ์ ๋ ฅ ์ฒ๋ฆฌ |
ํ์ฌ Feature ๋ชจ๋์ ์ง์์ ์ผ๋ก ๋ถ๋ฆฌ ๋ฐ ํ์ฅํ๋ฉฐ ์ํคํ ์ฒ๋ฅผ ๊ฐ์ ํ๊ณ ์์ต๋๋ค.
./gradlew assembleDebug # debug ๋น๋
./gradlew installDebug # ๊ธฐ๊ธฐ์ ์ค์น
./gradlew test # unit test (์ํคํ
์ฒ ๊ท์น ๊ฒ์ฌ ํฌํจ)
./gradlew lint # lint| buildType | applicationId | ์ฉ๋ |
|---|---|---|
| debug | com.makeus.mody.dev |
๊ฐ๋ฐ โ release ์ ๋์ ์ค์น ๊ฐ๋ฅ |
| release | com.makeus.mody |
๋ฐฐํฌ |
์นด์นด์ค ๋ค์ดํฐ๋ธ ํค๋ local.properties ์์ ์ฝ์ต๋๋ค(๋ฏธ์ค์ ์ ๋น ๊ฐ์ผ๋ก ๋น๋๋๋ฉฐ ์นด์นด์ค
๋ก๊ทธ์ธยท๊ณต์ ๊ฐ ๋์ํ์ง ์์ต๋๋ค).
KAKAO_NATIVE_KEY_DEV=...
KAKAO_NATIVE_KEY_PROD=...