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

от автора

Совсем скоро, 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 сентября, и до скорой встречи!


ссылка на оригинал статьи https://habr.com/ru/company/vk/blog/684108/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *