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

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

Обзор

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

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

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

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

Ресурсы

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