Scaling with Deeplinks on AndroidВ данной статье автор рассказывает как организовать код для поддержки большого количества диплинок в приложении.
🔸 Вначале у автора было всего 12 диплинок и соответственно был некоторый DeeplinkRouter, который помогал переходить на разные экраны:
interface Deeplink {
fun route(uri: Uri): Boolean
}
class NotificationsDeeplink(val navigator: Navigator) : Deeplink {
override fun route(uri: Uri): Boolean {
return if (uri.path == "/notifications") {
navigator.goToNotifications()
true
} else {
false
}
}
class DeeplinkRouter(
notificationsDeeplink: NotificationsDeeplink,
profileDeeplink: ProfileDeeplink,
// ...
) {
private val deeplinks = listOf(
notificationsDeeplink,
profileDeeplink,
// ...
)
fun route(intent: Intent) {
intent.data?.let { uri ->
deeplinks.find { it.route(uri) }
}
}
}
🔸 По мере роста количества диплинков начали возникать две большие проблемы:
1️⃣ Управление огромным количеством диплинков — почти 50
🔸 Нужно было внедрить в DeeplinkRouter. Решили эту проблему, введя аннотацию, которая позволит устранить повторяющийся код:
@RegisterDeeplinkclass NotificationsDeeplink(val navigator) : Deeplink {
override fun route(uri: Uri): Boolean {
// ...
}
}
class DeeplinkRouter(val register: DeeplinkRegister) {
fun route(intent: Intent) {
intent.data?.let { uri ->
register.deeplinks.find { it.route(uri) }
}
}
}
🔸 DeeplinkRegister создается во время сборки и отвечает за ведение списка диплинок
2️⃣ Некоторые диплинки чувствительны к порядку
🔸 Например:
class ArticleDeeplink(val navigator: Navigator) : Deeplink {
override fun route(uri: Uri): Boolean {
return if (uri.path?.startsWith("/article") == true && uri.pathSegments.size == 2) {
navigator.goToArticle(id = uri.pathSegments[1])
true
} else {
false
}
}
}
class CommentsDeeplink(val navigator: Navigator) : Deeplink {
override fun route(uri: Uri): Boolean {
return if (uri.path?.startsWith("/article") == true
&& uri.path?.endsWith("/comments") == true
&& uri.pathSegments.size == 3
) {
navigator.goToComments(id = uri.pathSegments[1])
true
} else {
false
}
}
}
🔸 Если пользователь открывает www.doximity.com/article/{id}/comments и сначала вызывается ArticleDeeplink, у CommentsDeeplink никогда не будет возможности обработать
🔸 Необходимо убедиться, что CommentsDeeplink размещается в списке перед ArticleDeeplink. Это хрупкая конструкция и ее становится все труднее решать в больших масштабах
🔸 Вместо этого было бы лучше объединить две диплинки и применить правило:
interface Deeplink {
fun route(uri: Uri) // no longer returns Boolean
}
@RegisterDeeplink("/article")
class ArticleDeeplink(val navigator: Navigator) : Deeplink {
override fun route(uri: Uri) {
when {
uri.pathSegments.size == 2 -> {
navigator.goToArticle(id = uri.pathSegments[1])
}
uri.pathSegments.size == 3 && uri.path?.endsWith("/comments") {
navigator.goToComments(id = uri.pathSegments[1])
}
}
}
}
class DeeplinkRouter(val register: DeeplinkRegister) {
fun route(intent: Intent) {
intent.data?.let { uri ->
val topLevelPath = uri.pathSegments.firstOrNull()
register.findDeeplink("/$topLevelPath")?.let { it.route(uri) }
}
}
}
🔸 DeeplinkRegister будет поддерживать сопоставление путей верхнего уровня с соответствующим определением диплинки. Если совпадение найдено, диплинка возвращается в DeeplinkRouter и получает входящий Uri. Это значительно снижает сложность управления диплинками
Далее автор рассказывает про работу с аннотациями и генерацию кода с помощью KotlinPoet 🚀