이번 프로젝트에서 처음으로 Multi Module Project를 도입했다. 다중 모듈 프로젝트란 하나의 app Module이 아닌, 여러개의 모듈로 구성되어 있는 프로젝트를 의미한다. 모듈 간 화면 전환을 다른 Feature 모듈의 의존성 없이 구현하기 위해 사용한 방법을 포스팅하려고 한다.
지금 하고 있는 프로젝트의 기본적인 View 단위는 Fragment로 구성되어 있고, 따로 소셜 로그인 및 로그아웃/회원삭제를 위해 Activity를 추가적으로 사용하고 있다. 따라서 화면 전환에서 필요한 기능은 여러개의 모듈을 넘나들면서, Activity를 전환해야할 뿐만 아니라, nav graph에 종속되지 않고 자유롭게 Fragment간 전환할 수 있는 기능을 필요로 한다. 예를 들면, 사전 모듈의 주류 정보에서 리뷰 모듈의 주류 리뷰로 전환해야 하는 상황, 따로 홈 화면을 띄우는 Activity에서 로그아웃을 할 수 있는 Activity로 전환하는 것이 있을 수 있다.
1. 모듈간 Fragment 전환하기
1-1) app Module Graph && NavHost 설정하기
App Module은 모든 feature 모듈을 참조하고 있는 모듈이자, 전체 navgiation grpah와 NavHost를 가지고 있으며, 다른 graph간 전환을 설정하는 모듈이다. 각 모듈의 graph는 모듈 간 grpah 전환을 위해 app 모듈의 graph에 include 되어 있어야 한다.
app/navigation/home_nav.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/home_nav"
app:startDestination="@id/analyze_nav">
<include app:graph="@navigation/review_nav" />
<include app:graph="@navigation/analyze_nav" />
<include app:graph="@navigation/setting_nav" />
<include app:graph="@navigation/dictionary_nav" />
</navigation>
1-2) feature Module Graph 설정하기
기본적으로 데이터 공유를 위해 Safe Args를 사용하며, 모듈안 Navigation을 위한 action과 전달 받을 argument 그리고 다른 모듈에서 해당 모듈로 전환을 위한 deep link를 설정할 수 있다.
review/navigation/review_nav.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/review_nav"
app:startDestination="@id/reviewListFragment">
<deepLink app:uri="BeJuRyu://feature/review" />
<fragment
android:id="@+id/reviewListFragment"
android:name="com.jaino.review.review_list.ReviewListFragment"
android:label="ReviewListFragment">
<argument
android:name="drinkId"
android:defaultValue="0L"/>
<deepLink
app:uri="BeJuRyu://feature/review/list?drinkId={drinkId}" />
<action
android:id="@+id/action_reviewListFragment_to_writeReviewFragment"
app:destination="@id/writeReviewFragment" >
<argument
android:name="drinkId"
app:argType="long" />
</action>
</fragment>
</navigation>
다른 모듈에서 reviewListFragment로 전환을 하기 위해서 Deep Link를 설정하였다. 또한 전달 받을 Arguement를 선언하고, deepLink Uri에 추가하였다.
1-3) deep Link로 navigation 구현하기
feature/dictionary/drinkInfoFragment.kt
@AndroidEntryPoint
class DrinkInfoFragment : Fragment() {
binding.goToReviewButton.setOnClickListener{
findNavController()
.navigate("BeJuRyu://feature/review/list?drinkId=${args.drinkId}".toUri())
}
}
만약 dictionaryInfoFragment에서 해당 reviewListFragment로 전환을 한다고 가정을 한다면, 위와 같이 간편하게 설정할 수 있다.
2. 모듈간 Activity 전환하기
2-1) Navigator 구현하기
Fragment 전환의 경우, Navigation libraray의 도움을 얻으면, 쉽게 구현할 수 있다. 그런데 Activity의 Intent를 각 모듈간 전환하기 위해서는 어떻게 해야할까?
먼저, 모든 기능 모듈을 참조하고 있는 모듈은 app 모듈이며, 해당 app 모듈에서 Intent전환을 구현할 수 있다. 그러나 app 모듈이 상위 모듈이기 때문에, 기능 모듈에서 app 모듈을 참조해서는 안된다. 따라서, 기능 모듈이 참조하는 Navigator의 interface는 core/common에 구현하고, 해당 Navigator의 구현체는 app 모듈에서 구현하였다.
core/common/navigator.kt
package com.jaino.common.navigation
import android.content.Intent
interface AppNavigator {
fun navigateToAuth(): Intent
fun navigateToSetting(): Intent
}
app/navigatorImpl.kt
package com.jaino.app.navigator
class AppNavigatorImpl @Inject constructor(
@ApplicationContext private val context: Context
): AppNavigator {
override fun navigateToAuth(): Intent = AuthActivity.getIntent(context)
override fun navigateToAccount(): Intent = AccountActivity.getIntent(context)
}
2-2) 의존성 주입 구현 및 Activity 전환하기
인터페이스와 구현체를 모두 구현하고, 의존성 주입을 설정한다. 기능 모듈에서 인터페이스를 참조하고, 참조할 때 마다 의존성을 주입하는 방식을 통해 Activity 전환을 구현할 수 있다.
AppModule.kt
package com.jaino.app.di
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
@ApplicationContext
fun provideApplication(application: Application) = application
@Provides
@Singleton
fun provideAppNavigator(appNavigator: AppNavigatorImpl): AppNavigator = appNavigator
}
app/feature/setting/settingFragment.kt
package com.jaino.setting
@AndroidEntryPoint
class SettingFragment : Fragment() {
@Inject
lateinit var appNavigator: AppNavigator
private fun navigateToAnalyze(){
startActivity(appNavigator.navigateToHome())
}
}
위와 같이, 다른 기능 모듈의 참조 및 app 모듈의 참조 없이 Activity Intent 전환을 구현할 수 있다.
3. 다른 모듈의 다른 Activity에 속한 Fragment로 전환하기
만약 다른 모듈의 다른 Activity에 속한 Fragment로 전환하고 싶다면 어떻게 해야할까?
예를 들면, A Activity의 A, B, C Fragment | B Activity의 D, E, F Fragment가 있고 ((A, B) Activity는 app module의 Activity가 아닐 때), A Fragment에서 C Fragment로 전환하는 상황이 있을 수 있다.
해당 상황에서 앞의 방법 2개를 활용하여 구현할 수 있다. Intent에 Navigation Deep Link를 추가하여 전달한다.
3-1) Naivgator에 매개변수를 추가한다.
app/navigatorImpl.kt
package com.jaino.app.navigator
class AppNavigatorImpl @Inject constructor(
@ApplicationContext private val context: Context
): AppNavigator {
override fun navigateToHome(destination: String): Intent =
HomeActivity.getIntent(context, destination)
}
3-2) 매개변수에 전환하고자 하는 Fragment의 deep Link를 전달한다.
acount/accountActivity.kt
package com.jaino.account
@AndroidEntryPoint
class AccountActivity : AppCompatActivity() {
private fun navigateToSetting(){
startActivity(appNavigator.navigateToHome("BeJuRyu://feature/setting"))
}
}
미리 전환하기를 원하는 Fragment의 deepLink를 설정하고, 설정한 deeplink를 전환하기를 원하는 Activit에 전달한다.
3-3) Intent 전환후, 전달받은 deep Link를 통해 전환한다.
app/homeActivity.kt
package com.jaino.app
@AndroidEntryPoint
class HomeActivity : AppCompatActivity() {
private lateinit var navController: NavController
private fun navigateToDestination(){
val destination = intent.getStringExtra("DESTINATION") ?: return
if(destination.isNotEmpty()){
navController.navigate(destination.toUri())
}
}
companion object {
fun getIntent(context: Context, destination: String): Intent {
return Intent(context, HomeActivity::class.java).apply {
putExtra("DESTINATION", destination)
}
}
}
}
직접적으로 다른 Fragment의 id나 action을 참조하여 전환한 것이 아닌, deepLink을 전달 받아, 다른 Activity의 원하는 Fragment까지의 전환을 구현할 수 있다.
결론
다중 모듈 프로젝트를 하면서 가장 처음에 만난 난관(?)이였다. 혼자서 생각도 많이 하고, Document나 타 프로젝트를 많이 뒤져보면서 공부했었다. 이렇게 전환이 까다로운 만큼, 타 모듈과 확실하게 독립된 느낌을 받을 수 있었다. 특히 해당 모듈에 대해서만 그래프를 만드는 것은 분업 및 다른 모듈의 영향이 적다는 강점이 확실히 느낄 수 있었다.
참고 자료
https://developer.android.com/guide/navigation/navigation-multi-module?hl=ko
다중 모듈 프로젝트를 위한 탐색 권장사항 | Android 개발자 | Android Developers
다중 모듈 프로젝트를 위한 탐색 권장사항 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 탐색 그래프는 다음을 원하는 대로 조합하여 구성할 수 있습니다.
developer.android.com