На этой странице рассказывается, как разбить код навигации на модули. Оно дополняет общие рекомендации по модульности приложений.
Обзор
Модульность кода навигации – это процесс разделения связанных клавиш навигации и контента, который они представляют, на отдельные модули. Это позволяет четко разделить обязанности и переключаться между разными функциями приложения.
Чтобы разбить код на модули, выполните следующие действия:
- Создайте два подмодуля:
apiиimplдля каждой функции в приложении. - Поместите клавиши навигации для каждой функции в ее модуль
api. - Поместите
entryProvidersи контент для каждой функции в соответствующий модульimpl. - Передайте
entryProvidersв основные модули приложения напрямую или с помощью внедрения зависимостей.
Разделение функций на подмодули API и реализации
Для каждой функции в приложении создайте два подмодуля с названиями api и impl (сокращение от "реализация"). В таблице ниже указано, где размещать код навигации.
Название модуля |
Содержит |
|
|
|
Контент для этой функции, в том числе определения для |
Этот подход позволяет одной функции переходить к другой, поскольку ее контент, содержащийся в модуле impl, зависит от клавиш навигации другого модуля, содержащегося в модуле api.
Как разделять записи навигации с помощью функций расширения
В Navigation 3 контент, по которому можно перемещаться, определяется с помощью записей навигации. Чтобы разделить эти записи на отдельные модули, создайте функции расширения для EntryProviderScope и переместите их в модуль impl для этой функции.
Они называются конструкторами записей.
В примере кода ниже показан конструктор записей, который создает две записи навигации.
// 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 DSL, при определении entryProvider в основном модуле приложения.
// import androidx.navigation3.runtime.entryProvider // import androidx.navigation3.ui.NavDisplay NavDisplay( entryProvider = entryProvider { featureAEntryBuilder() }, // ... )
Как использовать внедрение зависимостей, чтобы добавлять записи в основное приложение
В приведенном выше примере кода каждый конструктор записей вызывается напрямую из основного приложения с помощью DSL entryProvider. Если в вашем приложении много экранов или модулей функций, этот подход может оказаться неэффективным.
Чтобы решить эту проблему, добавьте в модуль приложения конструкторы записей из каждого модуля функций с помощью внедрения зависимостей.
Например, в следующем коде используются многосвязывания Dagger, а именно @IntoSet, чтобы внедрить конструкторы записей в Set, принадлежащий MainActivity. Затем они итеративно вызываются внутри 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() } }, // ... ) } } }
Если элементы навигации должны переходить на другие экраны, добавьте в каждую функцию создания объект, который может изменять состояние навигации приложения.
Модульность сопоставителей ссылок на контент
Если ваше приложение поддерживает ссылки на контент и состоит из модулей функций, каждый модуль функции должен определять 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 на модули, можно найти в следующих статьях: