Модулировать навигационный код

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

Обзор

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

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

  • Создайте два подмодуля: 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
        }
    }
}

Вызывайте эту функцию, используя DSL entryProvider , при определении 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, можно найти здесь: