phicdy devlog

Androidアプリ開発やその他技術系の記事をたまに書きます

AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

Android Architecture ComponentのNavigationとマルチモジュール

ここ何日かNavigationを使ったプロジェクトでどうマルチモジュールにするかを考えてました。

結論としては下図のように各featureはInterfaceのみに依存し、appで全画面遷移を解決するのが楽でした。

f:id:phicdy:20200828212710p:plain

各featureはInterfaceにのみ依存する

画面遷移するfeatureモジュールに依存してしまうと依存するfeatureモジュールに変更があるたびに再ビルドが必要になります。 マルチモジュールの利点である差分ビルドの高速化の恩恵が得られにくくなります。

また各feature間で相互に画面遷移する場合、モジュール間が循環依存になりビルドが通らなくなります。 そこで画面遷移のInterfaceのみを持つモジュールを作り、各featureモジュールはInterfaceのモジュールに依存するようにします。

appモジュールが全てを解決する

画面遷移のInterfaceの実装をどこに置くか考えるにあたってappモジュールが一番楽でした。 appモジュールはDIの都合上全てのモジュールに依存することが多いので画面遷移のInterfaceの実装での依存性解決が楽です。

SingleActivityとnav graphもappモジュールに置いてしまいます。nav graphも全てのFragmentを知っている必要があります。各featureモジュール間を依存させない方針にしたので全てのFragmentを知ることができるappモジュールでnav graphを解決するのが楽です。

実装

nav graphをappモジュールに用意します。

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/featureAFramgnet">

    <fragment
        android:id="@+id/featureAFramgnet"
        android:name="com.phicdy.example.feature.FeatureAFragment"
        android:label="featureA">
        <action
            android:id="@+id/action_featureAFragment_to_featureBFragment"
            app:destination="@id/featureBFramgnet" />
    </fragment>
    <fragment
        android:id="@+id/featureBFramgnet"
        android:name="com.phicdy.example.feature.FeatureBFragment"
        android:label="featureB" />
</navigation>

NavigationProviderのInterfaceを定義します。

interface NavigationProvider {
    fun featureAtoB(fragment: Fragment)
}

appモジュールで実装をDIで提供します。

@Module
object AppModule {

    @Provides
    @Singleton
    fun provideNavigationProvider(): NavigationProvider = object : IntentProvider {

        override fun featureAtoB(fragment: Fragment) {
            fragment.findNavController().navigate(R.id.action_featureAFragment_to_featureBFragment)
        }
    }
}

featureAではNavigationProviderで画面遷移します。

class FragmentA: DaggerFragment {

    @Inject
    lateinit var navigationProvider: NavigationProvider

    fun navigateToFragmentB() {
        navigationProvider.featureAtoB(this)
    }
}

おわりに

Navigationは得られる恩恵の割にはいろいろ複雑になるから無理していれなくてもいいんじゃないと思い始めました