Модульная структура кода навигации

На этой странице рассказывается, как разбить код навигации на модули. Оно дополняет общие рекомендации по модульности приложений.

Обзор

Модульность кода навигации – это процесс разделения связанных клавиш навигации и контента, который они представляют, на отдельные модули. Это позволяет четко разделить обязанности и переключаться между разными функциями приложения.

Чтобы разбить код на модули, выполните следующие действия:

  • Создайте два подмодуля: api и impl для каждой функции в приложении.
  • Поместите клавиши навигации для каждой функции в ее модуль api.
  • Поместите entryProviders и контент для каждой функции в соответствующий модуль impl.
  • Передайте entryProviders в основные модули приложения напрямую или с помощью внедрения зависимостей.

Разделение функций на подмодули API и реализации

Для каждой функции в приложении создайте два подмодуля с названиями api и impl (сокращение от "реализация"). В таблице ниже указано, где размещать код навигации.

Название модуля

Содержит

api

клавиши навигации;

impl

Контент для этой функции, в том числе определения для NavEntry и entryProvider. Также прочитайте статью о том, как сопоставлять ключи с контентом.

Этот подход позволяет одной функции переходить к другой, поскольку ее контент, содержащийся в модуле impl, зависит от клавиш навигации другого модуля, содержащегося в модуле api.

Диаграмма зависимостей модулей функций, показывающая, как модули `impl` могут зависеть от модулей `api`.
Рисунок 1. Диаграмма зависимостей модулей функций, показывающая, как модули реализации могут зависеть от модулей 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:

Модуль функции

Затем в основном модуле приложения, например в MainActivity, можно внедрить набор сопоставителей и использовать их для сопоставления входящих запросов:

Модуль приложения

Подробнее о том, как определять и обрабатывать ссылки на контент…

Ресурсы

Примеры кода, в которых показано, как разбить код Navigation 3 на модули, можно найти в следующих статьях: