Программирование
Навигация в многомодульном приложении с использованием глубоких ссылок
В этой статье мы реализуем навигации по нескольким функциональным модулям.
Привет, Android-разработчики, в этой статье мы реализуем навигации по нескольким функциональным модулям.
В чем проблема?
Недавно я настраивал non-compose многомодульный проект. В моем случае, я не был убежден в том, что надо использовать Single Activity Architecture, вместо этого я искал что-то вроде одной Activity для каждого функционального модуля, т.е. каждый функциональный модуль должен иметь как минимум одну Activity в качестве точки входа.
Навигация Jetpack может хорошо работать в многомодульном подходе. Тем не менее, она в основном основана на Фрагментах. Я думаю, что для больших проектов будет много случаев, когда было бы лучше иметь Активити вместо Фрагментов, потому что они будут иметь свой собственный жизненный цикл, и жизнь будет намного проще.
Почему глубокие ссылки?
Существуют и другие способы навигации по модулям, но основная причина выбора глубоких ссылок (DeepLink) заключается в том, что они не зависят от платформы. В будущем могут быть случаи, когда мы будем запускать некоторые функции и захотим, чтобы наши пользователи напрямую переходили к этому конкретному функциональному модулю, просто нажав на ссылку. Так гораздо проще и можно сравнительно быстрее масштабироваться.
Подход
- Мы создадим одну Activity для каждого модуля в качестве точки входа.
- Для каждой 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) | |
} |
В 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() | |
} | |
} |
Прежде чем перейти к фактической части навигации, нам нужно 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) | |
} |
Вот пример навигации по модулям:
Код
Весь код вы можете проверить в репозитории.
Еще про глубокие ссылки в Android
- Linkester: тестирование глубоких ссылок в приложении
- Создание глубоких ссылок для содержания приложения (документация)
- Начинаем работу с глубокими ссылками в Android
- Глубокие ссылки в Android с примерами
- Ссылки на приложения и глубокие ссылки с Android 12
- Ваши глубокие ссылки могут быть неработающими: Web Intent-ы и Android 12
-
Новости2 недели назад
Видео и подкасты о мобильной разработке 2025.14
-
Видео и подкасты для разработчиков4 недели назад
Javascript для бэкенда – отличная идея: Node.js, NPM, Typescript
-
Новости4 недели назад
Видео и подкасты о мобильной разработке 2025.12
-
Разработка3 недели назад
«Давайте просто…»: системные идеи, которые звучат хорошо, но почти никогда не работают