Как стать автором
Обновить
VK
Технологии, которые объединяют

Как мы делали суперприложение на основе Почты

Время на прочтение 5 мин
Количество просмотров 4.8K

Совсем скоро, 3 и 4 сентября в VK пройдёт новый Weekend Offer. В нём будет участвовать и наша команда — мы создаём суперприложение на основе почтового клиента Mail.ru. Хотим подробнее рассказать об этом проекте и о задачах, которые нужно будет решать нашим будущим коллегам :)

Год назад бизнес поставил нам задачу: интегрировать в приложение несколько других сервисов компании, чтобы пользователи могли одним нажатием переходить из сервиса в сервис. Ну, вы и сами знаете, для чего нужны суперы — для развития экосистемы и конкретных продуктов. И спустя два месяца мы запустили в эксплуатацию суперприложение на основе почтового клиента Mail.Ru.

Архитектура

Интегрировать в суперприложение нужно было Облако, Марусю, Новости, Календарь и Задачи, а заодно требовалось создать техническую основу, позволяющую легко и быстро добавлять новые сервисы, как вставить вилку в розетку. Архитектурно мы отказались от конкретного приложения-ядра, и решили сделать так, чтобы хостом суперприложения мог стать любой из мобильных клиентов сервисов.

С точки зрения разработчиков мы хотели сделать универсальный механизм интеграции, чтобы авторам хоста и подключаемых сервисов не нужно было разбираться в устройстве продуктов друг друга, достаточно соответствовать общему набору правил. Задача осложнялась тем, что подключаемые к суперприложению сервисы имели разную реализацию. Кто-то предоставлял готовое SDK (Маруся и Пульс) и достаточно было просто встроить его; какие-то сервисы пришлось подключать через WebView (Календарь и Задачи); а кому-то пришлось писать код с нуля, чтобы интегрироваться в суперприложение (Облако).

Мы пришли к единому интерфейсу адаптера для всех сервисов, который необходимо реализовать, чтобы вкладка появилась в нашем приложении. 

Модули

Для перехода на новую архитектуру мы реализовали хост-портал и подключаемые сервисы в виде модулей. В какой-то момент мы вспомнили, что сервисы могут вызывать функции других сервисов. Например, Облако может открыть форму написания электронного письма и приложить к нему какой-нибудь файл. Поэтому сделали отдельными модулями реализации интерфейсов, а позднее — модули с самими интерфейсами. 

White label

Также мы подумали о том, что не всем брендам компании могут быть нужны все сервисы, и придумали механизм выборочного исключения из приложения кода определённых вкладок. Для брендов по-отдельности регистрируем поддерживаемые его сервисом функции, и каждый модуль сервиса ссылается только на API-модули и запрашивает доступность определённых функций. Если какая-то функция недоступна, то приложение не показывает соответствующие элементы интерфейса — кнопки, формы и т.д. Например, если в суперприложении не будет вкладки Облака, то не получится приложить облачный файл в форме написания письма в Почте. 

Вкладки и фоновые сервисы

Сначала мы считали, что каждый новый сервис будет отображаться в интерфейсе в виде отдельной вкладки. Но потом поняли, что некоторым сервисам вкладка не нужна, они должны работать в фоновом режиме. Самый яркий пример — поиск, это тоже отдельный сервис, но он интегрируется во вкладки других сервисов и не имеет собственной. Пришлось немного переделать интерфейс адаптера.

При переключении вкладок мы не просто подставляем экраны, но и меняем содержимое общих компонентов — верхней и нижней панели: меняется доступный набор действий. То есть в зависимости от выбранного сервиса меняется глобальный контекст суперприложения. Фоновые сервисы этот механизм не используют.

Приоритизация

Чтобы сохранить высокую работоспособность приложения, мы не могли держать в памяти сразу все сервисы. Поэтому придумали систему приоритизирования вкладок: каждому сервису мы вручную выставляем приоритет, и если пользователь переходит с низкоприоритетной вкладки на высокоприоритетную, а затем с неё опять на высокоприоритетную, то мы выгружаем из памяти сервис с низким приоритетом.

Навигация

Выше мы упомянули, что вкладки взаимодействуют друг с другом с помощью API. Но есть и второй механизм — переход по ссылкам, deep link-ам. Он предназначен для навигации пользователей между сервисами. Мы придумали формат ссылок, который должны поддерживать все вкладки, и каждая из них может запросить переход по такой ссылке. Тогда суперприложение открывает экран целевого сервиса и, при необходимости, запускает запрошенную функцию. Например, если вы выберете в Облаке файл и нажмёте кнопку «Отправить в письме», то через deep link Облако обратится к Почте, программа откроет форму написания и прикрепит выбранный файл.

Ещё на основе deep link-ов работают кнопки в push-уведомлениях, чтобы можно было прямо из сообщения одним нажатием перейти в соответствующий сервис и запустить фичу.

Работа с зависимостями

В работе мы столкнулись с тем, что все вкладки так или иначе используют общие библиотеки, у которых могут быть разные версии. Иногда возникали несовместимости: какая-нибудь вкладка обновляла внутри себя некую библиотеку, и в результате приложение могло не собраться, либо в runtime могли возникнуть ошибки. Мы решили это с помощью самописного Gradle-плагина, который валидирует версии зависимостей при сборке проекта (включая транзитивные зависимости). У нас имеется эталонный каталог с заданными версиями зависимостей, с которыми приложение работает стабильно. При сборке приложения плагин проверяет, что в итоговую сборку не попали зависимости с версией выше указанной в каталоге. Если такие зависимости будут обнаружены, то сборка завершится с ошибкой. Внедрение плагина позволило нам контролировать обновления библиотек и избежать неявного поднятия их версии.

Плагин проверяет каждую зависимость:

class DependenciesPlugin: Plugin<Project> {
    override fun apply(project: Project) {
        project.configurations.all { action ->
            action.resolutionStrategy.eachDependency { rule ->
                MailResolveStrategy.verifyDependency(rule)
            }
        }
    }
}

Валидация заключается в поиске эталонной версии в каталоге и сравнении её с текущей версией. Если текущая версия выше, то бросаем исключение.

@JvmStatic
fun verifyDependency(details: DependencyResolveDetails) {
    val group = details.requested.group
    val name = details.requested.name

    val currentVersion: String? = details.requested.version
    val catalogVersion: VersionNumber? = findInCatalog(group, name)?.version

    if (currentVersion != null && catalogVersion != null) {
        val moreRecent = maxOf(VersionNumber.parse(currentVersion), catalogVersion)
        if (moreRecent != catalogVersion) {
            val msg = "$group:$name with version $currentVersion is not allowed " +
                "because its version is greater than version $catalogVersion from the catalog."
            throw RuntimeException(msg)
        }   
    }
}

Аналитика

Сейчас мы централизованно собираем телеметрию со всех вкладок, а авторы хост-приложения выбирают, в каком приложении анализировать собранные данные. Так в разы проще, чем собирать отдельно с каждой вкладки и слать в разные инструменты. 

Что мы уже выяснили по результатам анализа метрик? Самое главное — пользователи, активировавшие новую версию, остаются на ней, и частота их взаимодействия с продуктом растёт. Судя по опросам, новинка людям понравилась. Опросы подтвердили удовлетворённость обновлением. После запуска супераппа использование не-почтовых сервисов в приложении выросло более чем в 10 раз.

* * *

Друзья, мы приглашаем Android-разработчиков в нашу команду, работы много — нужно развивать и расширять наше суперприложение. Записывайтесь на Weekend Offer, который пройдёт 3 и 4 сентября, и до скорой встречи!

Теги:
Хабы:
+14
Комментарии 10
Комментарии Комментарии 10

Публикации

Информация

Сайт
team.vk.company
Дата регистрации
Дата основания
Численность
свыше 10 000 человек
Местоположение
Россия
Представитель
Руслан Дзасохов