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

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

Обзор

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

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

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

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

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

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

Содержит

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, см. в следующих статьях: