실제 예제로 연습해 보겠습니다. 저는 아래 프로젝트를 활용했고, 살짝 코드를 변경했습니다.
https://github.com/philipplackner/DictionaryYT/tree/app
밑은 제가 변경한 코드입니다. (Mapper object 추가 및 coomon, core 패키지 분리)
https://github.com/HanBI24/DictCacheApp_Clean_Architecture/tree/non_module
해당 프로젝트에 간단히 소개드리자면, 사용자가 검색어를 입력하면 Dictionary API를 HTTP 통신해서 가져오고, 한 번 검색된 검색어는 로컬 DB에 저장(Room)하여 오프라인 상태에서도 볼 수 있는 Cache 기능을 수행하는 프로젝트입니다.
모듈화를 진행하기 전, 유튜브 한 번 보시고 오는 것을 추천드립니다 ! (해당 프로젝트 구성 유튜브)
모듈화 하기 전 프로젝트 패키지 구조는 다음과 같습니다.
보통은 제일 상위에 data, domain, presentation이 위치하겠지만, 이는 앱의 규모가 커질수록 프로젝트를 더욱 복잡하게 만듭니다. 이를 해결하기 위해 각 화면 별로 feature_(screen_name)로 나누어서 진행했습니다. 또한, common과 core 패키지가 보일 것입니다. 이것이 하는 역할은 다음과 같습니다.
common: 해당 프로젝트에서 자주 쓰이는 간단한 기능들을 정의해 놓은 공간입니다.
(BaseURL을 저장한 Constants.kt 파일과 SnackBar, Toast 등을 호출할 수 있는 UIEvent.kt 파일이 있습니다.)
core(library): core 또는 library라고 합니다. 여기엔 핵심 비즈니스 로직이 추가될 수 있기 때문에 domain layer와 같이 순수 Java, Kotlin 코드 또는 Coroutine만 작성할 수 있습니다.
(네트워크 통신 상태에 따른 처리를 하기 위해 Resource.kt 파일이 위치해 있습니다.)
이것을 최종적으로 모듈화 하면 다음과 같이 구성됩니다.
거의 똑같다고 볼 수 있죠? 바로 진행하겠습니다.
먼저 모듈을 생성해 보겠습니다. 모듈을 생성하기 위해 Android -> Project로 변경합니다.
그리고 최상위 모듈인 app 모듈을 feature_dictionary로 이름을 변경하겠습니다.
[ app 선택 후 Shift + F6 => Rename module 선택 => feature_dictionary로 변경 ]
잠시 기다리면 이름이 변경됩니다. 이후 나머지 모듈들도 추가해 보겠습니다.
[ 해당 프로젝트 우 클릭 => New => Module 선택 => Android Library 선택 후 Module name에 입력 ]
그리고 common, core 모듈을 추가해 줍니다. 추가한 결과는 다음과 같습니다.
이번엔 feature_dictionary 모듈에 data, domain, presentation 모듈을 추가하겠습니다.
[ feature_dictionary 우 클릭 => New => Module 선택 => Android Library 선택 후 Module name에 입력 ]
!! 주의 !!
mylibrary 이 부분만 변경해야 합니다. 상위 모듈 표시를 당연히 해줘야 합니다!!
모두 추가하셨으면, 다음과 같이 모듈 구조가 완성됩니다. 이제 다시 Project -> Android로 변경해 봅시다.
위 사진을 보시면 굉장히 많은 build.gradle 파일이 보이실 겁니다. 이것은 Project 수준의 파일과 각 모듈에 대한 파일입니다. 이 상태에서 실행해도 문제없이 실행됩니다. 아직 어떠한 의존성도 추가하지 않았고, app을 feature_dictionary로 이름만 바꿨기 때문입니다.
이제 각 모듈에 대한 의존성과 라이브러리를 추가해 보도록 하겠습니다.
- common 모듈
해당 모듈은 어떠한 의존성과 라이브러리를 갖지 않습니다.
- core 모듈
해당 모듈은 어떠한 의존성과 라이브러리를 갖지 않습니다.
(비즈니스 로직을 담당하기 때문에 순수 Java와 Kotlin, Coroutine 코드만 작성할 수 있습니다.)
- feature_dictionary 모듈
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
dependencies {
implementation project(':common')
implementation project(':feature_dictionary:presentation')
implementation project(':feature_dictionary:domain')
implementation project(':feature_dictionary:data')
...
...
...
// Compose dependencies
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
// Coroutine Lifecycle Scopes
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
//Dagger - Hilt
implementation "com.google.dagger:hilt-android:2.38.1"
kapt "com.google.dagger:hilt-android-compiler:2.37"
kapt "androidx.hilt:hilt-compiler:1.0.0"
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0-alpha03'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"
// Room
implementation "androidx.room:room-runtime:2.5.0"
kapt "androidx.room:room-compiler:2.5.0"
// Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:2.3.0"
}
feature_dictionary 모듈은 common, presentation, domain 모듈을 의존하기 때문에 모듈을 추가해야 합니다.
모듈을 추가하는 코드는 implementation project(':module_name')이며, 상위 프로젝트의 모듈을 추가하려면 콜론(:)을 기준으로 작성하시면 됩니다.
또한, 해당 프로젝트는 Jetpack Compose, Room, Retrofit, Coroutine, Hilt를 활용한 프로젝트이기 때문에 하단의 라이브러리를 추가합니다.
- presentation 모듈
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
dependencies {
implementation project(':core')
implementation project(':common')
implementation project(':feature_dictionary:domain')
implementation project(':feature_dictionary:data')
...
...
...
// Compose dependencies
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
// Coroutine Lifecycle Scopes
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
//Dagger - Hilt
// hilt 최신 버전 사용하면 conflict 발생
implementation "com.google.dagger:hilt-android:2.38.1"
kapt "com.google.dagger:hilt-android-compiler:2.37"
kapt "androidx.hilt:hilt-compiler:1.0.0"
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0-alpha03'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"
// Room
implementation "androidx.room:room-runtime:2.5.0"
kapt "androidx.room:room-compiler:2.5.0"
// Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:2.5.0"
}
presentation 모듈은 core, domain, domain, data 모듈에 의존합니다. 해당 모듈도 필요한 라이브러리를 추가해 줍니다.
- domain 모듈
dependencies {
implementation project(':core')
...
}
domain 모듈은 어느 모듈에 의존하지 않지만, repository interface를 구현하기 위해 core 모듈 의존성을 추가합니다.
Clean Architecture에서 위배되는 행위인 것 같아 다른 자료를 찾아보았더니 이렇게 구현한 사람도 있긴 했습니다. 이 부분에 대해선 좀 더 공부가 필요해 보입니다.
물론 core 모듈도 다른 android 라이브러리나 이런 것이 포함된 코드가 아니기 때문에 큰 문제는 없다고 생각합니다...
- data 모듈
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
dependencies {
implementation project(':core')
implementation project(':common')
implementation project(':feature_dictionary:domain')
...
...
...
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.okhttp3:okhttp:5.0.0-alpha.2"
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.2"
// Room
implementation "androidx.room:room-runtime:2.5.0"
kapt "androidx.room:room-compiler:2.5.0"
// Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:2.5.0"
}
마찬가지로 data 모듈도 필요한 의존성과 라이브러리를 추가해 줍니다. 여기까지 마치셨으면 "Sync Now"를 눌러 동기화를 진행합니다. 그리고 이상이 없는지 실행해 보시고, 앱이 정상적으로 작동하면 여기까지 잘 진행하신 겁니다!!
(만약 Room 관련 에러가 발생하면 Room 버전을 최신으로 업데이트해보세요.)
이제 해당 패키지에 있는 파일들을 전부 각 모듈에 맞게 이동시켜 주면 됩니다. 패키지 내의 하위 패키지 및 파일들을 모두 옮겨주시면 됩니다. 위치는 당연히 java(또는 kotlin) / package_name / module_name에 위치시켜 주면 되겠죠?
(Refactor가 뜨면 Refactor 누르고, 무슨 경고창이 뜨면 Continue 누르면 됩니다.)
이동시킨 후 남아있는 패키지는 헷갈리지 않게 삭제하면 됩니다.
여기서 주의해야 할 부분은 presentation의 view 패키지 내부에 있는 파일들은 feature_dictionary 모듈에 넣어주셔야 합니다. presentation 모듈에 넣으시면 안 됩니다 !!
이유는 해당 프로젝트에서 따로 navigation 처리를 하지 않았기 때문입니다. 만약 presentation 모듈로 옮기시면 AndroidManifest.xml에서 오류가 발생할 것입니다. 잘 따라오셨다면 다음과 같이 presentation 모듈 구조가 완성되어 있을 것입니다.
이제 한 번 실행을 해보면 정상적으로 모든 기능이 실행될 것입니다.
감사합니다.
'Android > Android' 카테고리의 다른 글
[Compose] Naver Map 현재 위치로 이동하기 (0) | 2023.04.08 |
---|---|
[Android] Compose로 Naver Map 띄우기 (0) | 2023.04.05 |
[Android] Clean Architecture 모듈화 해보기 - 1 (0) | 2023.03.22 |
[Jetpack Compose] Row와 Column을 이용해서 Grid 화면 만들기 (0) | 2023.03.10 |
[Android][@Qualifier] Dagger Hilt로 같은 Retrofit 객체를 여러 번 호출할 때 사용하는 방법 (0) | 2021.08.27 |