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

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

Обзор

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

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

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

Если элементы навигации требуют навигации — например, содержат элементы пользовательского интерфейса, которые ведут к новым экранам, — внедрите в каждую функцию конструктора объект, способный изменять состояние навигации приложения.

Ресурсы

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