Connect with us

Программирование

Навигация в многомодульном приложении с использованием глубоких ссылок

В этой статье мы реализуем навигации по нескольким функциональным модулям.

Опубликовано

/

     
     

Привет, Android-разработчики, в этой статье мы реализуем навигации по нескольким функциональным модулям.

В чем проблема?

Недавно я настраивал non-compose многомодульный проект. В моем случае, я не был убежден в том, что надо использовать Single Activity Architecture, вместо этого я искал что-то вроде одной Activity для каждого функционального модуля, т.е. каждый функциональный модуль должен иметь как минимум одну Activity в качестве точки входа.

Навигация Jetpack может хорошо работать в многомодульном подходе. Тем не менее, она в основном основана на Фрагментах. Я думаю, что для больших проектов будет много случаев, когда было бы лучше иметь Активити вместо Фрагментов, потому что они будут иметь свой собственный жизненный цикл, и жизнь будет намного проще.

Почему глубокие ссылки?

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

Подход

  1. Мы создадим одну Activity для каждого модуля в качестве точки входа.
  2. Для каждой Activity мы создадим процессор, который запускает одно конкретное действие после того, как убедится, что входящая глубокая ссылка подходит для этого действия.

Хватит говорить, давайте сделаем.

Навигация в многомодульном приложении с использованием глубоких ссылок

Прежде всего, настройте non-compose проект и создайте два функциональных модуля с именами feature_01 и feature_02 и один core модуль помимо app модуля.

Навигация в многомодульном приложении с использованием глубоких ссылок

Зависимости модулей можно определить так:

  • app зависит от всех модулей core, feature_01 и feature_02
  • feature_01 зависит от core
  • feature_02 зависит от core
  • core ни от чего не зависит

Здесь core является общим модулем для всех остальных модулей.

Затем внутри core модуля создайте пакет для навигации, который будет содержать некоторые классы и интерфейсы, которые будут использоваться для обработки глубоких ссылок  для каждого из функциональных модулей.

Прежде всего, создайте интерфейс DeeplinkProcessor. У него будет две функции: одна для сопоставления с deeplink, а другая для выполнения запуска Активити после сопоставления.

interface DeeplinkProcessor {
fun matches(deeplink: String): Boolean
fun execute(deeplink: String)
}

Затем создайте обработчик, который будет иметь только одну функцию, process, которая будет отвечать за запуск процессора.

interface DeeplinkHandler {
fun process(deeplink: String): Boolean
}

После этого мы создадим DefaultHandler, который будет реализацией этого DeeplinkHandler. Он вызовет функцию выполнения для первого найденного процессора, соответствующего диплинку.

class DefaultDeeplinkHandler constructor(
private val processors: Set<@JvmSuppressWildcards DeeplinkProcessor>
): DeeplinkHandler {
override fun process(deeplink: String): Boolean {
processors.forEach {
if (it.matches(deeplink)) {
it.execute(deeplink)
return true
}
}
return false
}
}

Теперь мы создадим Активити для каждого функционального модуля, а именно Feature01Activity и Feature02Activity. В соответствии с ними мы также создадим DeeplinkProcessor для них обоих, а именно Feature01DeeplinkProcessor и Feature02DeeplinkProcessor.

@Singleton
class Feature01DeeplinkProcessor @Inject constructor(
private val context: Context
) : DeeplinkProcessor {
override fun matches(deeplink: String): Boolean {
return deeplink.contains("/feat01")
}
override fun execute(deeplink: String) {
val intent = Intent(context, Feature01Activity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}

Для Feature02DeeplinkProcessor мы также можем обрабатывать некоторые дополнительные данные, идущие вместе со ссылкой.

@Singleton
class Feature02DeeplinkProcessor @Inject constructor(
private val context: Context
) : DeeplinkProcessor {
override fun matches(deeplink: String): Boolean {
return deeplink.contains("/feat02")
}
override fun execute(deeplink: String) {
val extraData = deeplink.split("/feat02/").getOrNull(1)
val intent = Intent(context, Feature02Activity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.putExtra("extraData",extraData)
context.startActivity(intent)
}
}

На данный момент у нас есть все строительные блоки для перехода от feature_01 к feature_02. Теперь нам осталось только совместить все это в действии.

Поддержка экземпляров всех этих процессоров может быть утомительной, поскольку в нашем приложении может быть n модулей.

Чтобы помочь с этим, мы будем использовать Dagger-Hilt для предоставления и выполнения всех процессоров, определенных в модулях.

В нашем app модуле создайте объект с именем AppModule, который предоставит контекст приложения и обработчик глубоких ссылок для обработки всех определенных процессоров.

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
@Provides
@Singleton
fun providesContext(@ApplicationContext context: Context): Context = context
@Provides
@Singleton
fun providesDefaultDeeplinkHandler(
processors: Set<@JvmSuppressWildcards DeeplinkProcessor>
): DeeplinkHandler = DefaultDeeplinkHandler(processors)
}
view raw AppModule.kt hosted with ❤ by GitHub

В ProvideDefaultDeeplinkHandler() нам нужно предоставить набор процессоров, но поскольку DeeplinkProcessor — это интерфейс, мы не можем просто предоставить его путем инстанцирования, поэтому нам нужно привязать его с помощью Hilt.

@Module
@InstallIn(SingletonComponent::class)
interface DeepLinkProcessorModule {
@Binds
@IntoSet
fun bindFeat01Processors(
feature01DeeplinkProcessor: Feature01DeeplinkProcessor
): DeeplinkProcessor
@Binds
@IntoSet
fun bindFeat02Processors(
feature02DeeplinkProcessor: Feature02DeeplinkProcessor
): DeeplinkProcessor
}

Это привяжет все процессоры к набору, необходимому для создания DefaultHandler.

Теперь мы можем внедрить этот обработчик по умолчанию внутрь нашей MainActivity и обрабатывать глубокие ссылки.

private fun handleIntent(intent: Intent) {
intent.data?.toString()?.let {
deeplinkHandler.process(it)
finish()
}
}
view raw MainActivity.kt hosted with ❤ by GitHub

Прежде чем перейти к фактической части навигации, нам нужно scheme и host для MainActivity в файле manifest.xml.

<intent-filter
android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="raystatic"
android:host="multi.module.app"/>
</intent-filter>

Теперь мы можем попробовать сделать навигацию следующим образом:

binding.btnGoto.setOnClickListener {
// Navigate to feature_01
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("raystatic://multi.module.app/feat01"))
startActivity(intent)
// Navigate to feature_02
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("raystatic://multi.module.app/feat02"))
startActivity(intent)
}
view raw MainActivity.kt hosted with ❤ by GitHub

Вот пример навигации по модулям:

Навигация в многомодульном приложении с использованием глубоких ссылок

Код

Весь код вы можете проверить в репозитории.

Еще про глубокие ссылки в Android

Источник

Если вы нашли опечатку - выделите ее и нажмите Ctrl + Enter! Для связи с нами вы можете использовать info@apptractor.ru.
Telegram

Популярное

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: