このページでは、ナビゲーション コードをモジュール化するためのガイドを提供します。このガイドは、アプリのモジュール化に関する一般的なガイダンスを補完することを目的としています。
概要
ナビゲーション コードのモジュール化とは、関連するナビゲーション キーと、それらが表すコンテンツを個別のモジュールに分割するプロセスです。これにより、責任が明確に分離され、アプリのさまざまな機能を切り替えることができます。
ナビゲーション コードをモジュール化する手順は次のとおりです。
- アプリの各機能に対して
apiとimplの 2 つのサブモジュールを作成する - 各機能のナビゲーション キーをそれぞれの
apiモジュールに配置 - 各機能の
entryProvidersとナビゲーション可能なコンテンツを、関連するimplモジュールに配置します。 - メインアプリ モジュールに
entryProvidersを直接提供するか、依存関係の挿入を使用して提供します。
機能を api サブモジュールと実装サブモジュールに分離
アプリの各機能について、api と impl(「実装」の略)という 2 つのサブモジュールを作成します。次の表を参考にして、ナビゲーション コードを配置する場所を決定してください。
モジュール名 |
以下を含む |
|
|
|
|
このアプローチでは、ある機能のコンテンツ(その機能の impl モジュールに含まれる)が、別のモジュールのナビゲーション キー(そのモジュールの api モジュールに含まれる)に依存できるようにすることで、ある機能から別の機能に移動できます。
拡張関数を使用してナビゲーション エントリを分離する
Navigation 3 では、ナビゲーション可能なコンテンツはナビゲーション エントリを使用して定義されます。これらのエントリを個別のモジュールに分離するには、EntryProviderScope に拡張関数を作成し、その機能の impl モジュールに移動します。これらは「エントリ ビルダー」と呼ばれます。
次のコード例は、2 つのナビゲーション エントリをビルドするエントリ ビルダーを示しています。
// import androidx.navigation3.runtime.EntryProviderScope // import androidx.navigation3.runtime.NavKey fun EntryProviderScope<NavKey>.featureAEntryBuilder() { entry<KeyA> { ContentRed("Screen A") { // Content for screen A } } entry<KeyA2> { ContentGreen("Screen A2") { // Content for screen A2 } } }
メインアプリ モジュールで entryProvider を定義するときに、entryProvider DSL を使用してその関数を呼び出します。
// import androidx.navigation3.runtime.entryProvider // import androidx.navigation3.ui.NavDisplay NavDisplay( entryProvider = entryProvider { featureAEntryBuilder() }, // ... )
依存性注入を使用してメインアプリにエントリを追加する
上記のコード例では、各エントリ ビルダーは entryProvider DSL を使用してメインアプリから直接呼び出されます。アプリに多くの画面や機能モジュールがある場合、このパターンはうまくスケーリングできない可能性があります。
これを解決するには、各機能モジュールが依存関係の注入を使用して、エントリ ビルダーをアプリの Activity に提供するようにします。
たとえば、次のコードでは、Dagger マルチバインディング(具体的には @IntoSet)を使用して、MainActivity が所有する Set にエントリ ビルダーを挿入します。これらは entryProvider 内で繰り返し呼び出されるため、多数のエントリ ビルダー関数を明示的に呼び出す必要がなくなります。
機能モジュール
// import dagger.Module // import dagger.Provides // import dagger.hilt.InstallIn // import dagger.hilt.android.components.ActivityRetainedComponent // import dagger.multibindings.IntoSet @Module @InstallIn(ActivityRetainedComponent::class) object FeatureAModule { @IntoSet @Provides fun provideFeatureAEntryBuilder() : EntryProviderScope<NavKey>.() -> Unit = { featureAEntryBuilder() } }
アプリ モジュール
// import android.os.Bundle // import androidx.activity.ComponentActivity // import androidx.activity.compose.setContent // import androidx.navigation3.runtime.EntryProviderScope // import androidx.navigation3.runtime.NavKey // import androidx.navigation3.runtime.entryProvider // import androidx.navigation3.ui.NavDisplay // import javax.inject.Inject class MainActivity : ComponentActivity() { @Inject lateinit var entryBuilders: Set<@JvmSuppressWildcards EntryProviderScope<NavKey>.() -> Unit> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { NavDisplay( entryProvider = entryProvider { entryBuilders.forEach { builder -> this.builder() } }, // ... ) } } }
ナビゲーション エントリがナビゲートする必要がある場合(たとえば、新しい画面に移動する UI 要素が含まれている場合)、アプリのナビゲーション状態を変更できるオブジェクトを各ビルダー関数に挿入します。
ディープリンク マッチャーをモジュール化する
アプリがディープリンクをサポートし、モジュール化されている場合、各機能モジュールは、所有するリンク先の DeepLinkMatcher インスタンスを定義する必要があります。
アプリ全体からすべてのマッチャーを照合するには、依存関係注入マルチバインディングを使用します。たとえば、各機能モジュールは、そのマッチャーを Dagger @IntoSet マルチバインディングに提供できます。
機能モジュール
@Module @InstallIn(ActivityRetainedComponent::class) object FeatureADeepLinkModule { @IntoSet @Provides fun provideUserMatcher(): DeepLinkMatcher<*, *> { return UriDeepLinkMatcher( DeepLinkUri("www.example.com/users/{id}"), serializer<UserProfileKey>() ) } }
次に、MainActivity などのメインアプリ モジュールで、マッチャーのセットを挿入し、それらを使用して受信リクエストを照合できます。
アプリ モジュール
@AndroidEntryPoint class MainActivityWithDI : ComponentActivity() { @Inject lateinit var deepLinkMatchers: Set<@JvmSuppressWildcards DeepLinkMatcher<*, *>> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val request = DeepLinkRequest(intent = intent) val matchResult = deepLinkMatchers .mapNotNull { it.match(request) } .maxOrNull() val backStack = when (matchResult) { null -> listOf(HomeKey) is BackStackMatchResult<*, *> -> { @Suppress("UNCHECKED_CAST") matchResult.backStack as List<NavKey> } else -> listOf(matchResult.key) } } }
ディープリンクの定義と処理について詳しくは、ディープリンクをサポートするをご覧ください。
リソース
Navigation 3 コードをモジュール化する方法を示すコードサンプルについては、以下をご覧ください。