![](https://habrastorage.org/getpro/habr/upload_files/16f/a83/c10/16fa83c10ea311f7d7dcf073ab389869.png)
Олды помнят, как ещё каких-то 10-15 лет назад заказывали пиццу по телефону, диктуя адрес операторам кол-центра. Мало кто тогда думал, что отсутствие подсказок адресов и карты на сайте с определением геолокации, а уж тем более в мобильном приложении, будет вызывать у нас реакцию «да сложно, что ли, нормальную карту сделать?».
Но вот мы здесь, рассказываем, как в Додо Пицце лишь недавно появилась уже привычная многим адресная система. Как так вышло — история долгая и не всем интересная. Скажу только, что переделывать и менять как-то работающее старое на новое в большой системе — тот ещё квест. В итоге нам пришлось зарыться достаточно глубоко во внутренности Dodo IS, чтобы можно было добавлять адреса с помощью карты. В этой статье расскажу, с чего мы начинали и как реализовали эту фичу в нашем iOS-приложении.
Назад в прошлое
Если вы пользовались приложением Додо Пиццы, то могли заметить, что раньше в нём невозможно было редактировать существующие адреса — только удалить или создать новые; не было привычной карты в приложении, чтобы подвигать пин или определить адрес по геолокации.
![Клиент не мог отредактировать поля, только только удалить и создать новый адрес. Клиент не мог отредактировать поля, только только удалить и создать новый адрес.](https://habrastorage.org/getpro/habr/upload_files/ed6/c0d/164/ed6c0d164ed00b1f6612f60692cfe8b4.png)
Не было и поиска, как, например, в картах Яндекса. Клиентам приходилось вводить адрес вручную, и он должен был совпадать с тем адресом, который был внесён в нашу систему. Например, если у нас записано «ул. Ломоносова», а клиент вводит полностью слово «улица», то адрес вообще мог не найтись. Это происходило из-за особенностей старой адресной системы в Dodo IS.
![Старая адресная система умеет задавать зону доставки в виде списка улиц и домов. Старая адресная система умеет задавать зону доставки в виде списка улиц и домов.](https://habrastorage.org/getpro/habr/upload_files/8c1/d48/843/8c1d48843e0c4a7ea6d621d91f1eedab.png)
Так сложилось, что с самых первых дней адреса у нас хранились не в виде координат, а в виде строк, например «ул. Пушкинская, д. 7». И такие строчки мы собирали каждый раз при открытии новой пиццерии и её зоны доставки. Один дом — одна строчка, десять домов одной улицы — десять строчек. Вам уже больно это читать? А представьте, что испытывали мы.
Помимо сложностей на стороне клиента, это ещё и тормозило открытие новых пиццерий, потому что на создание каждой новой зоны доставки могло уходить до трёх месяцев. Редактировать такой массив данных при изменении адресов тоже дело не пяти минут, а если ещё вдруг кто-то ошибся и неправильно внёс данные...
Но хоть мы и осознавали все недостатки и боль такой реализации, хоть пользовательский опыт и хромал, эта система работала. До тех пор, пока мы не открылись в Великобритании.
Изменение работы с адресами в Великобритании
Дело в том, что британцы больше привыкли вводить свой почтовый индекс (postcode), а не название улицы. И наш прямо противоположный подход доставлял ещё больше неудобств для пользователей. Тогда мы впервые сделали специальный экран: основной акцент был на вводе индекса, остальную информацию можно было добавить позже. Тут ещё нужно отметить, что в таком варианте выбор города вообще пропадал из сценария ввода адреса, и это позволяло сокращать путь пользователя от выбора адреса до меню.
![Первый макет экрана ввода адреса для Великобритании. Первый макет экрана ввода адреса для Великобритании.](https://habrastorage.org/getpro/habr/upload_files/53d/c84/38d/53dc8438dfd2f0f8f6b8f141070dd467.png)
MVP новой адресной системы
Реальность уже не просто намекала, а трубила изо всех сил: «Пора!». Настало время переосмыслить концепцию адресов во всех странах и начать работать с ними, как все большие дяди: карта, координаты, геолокация.
Какие цели мы ставили в первую очередь:
сделать удобным выбор адреса в мобильном приложении для наших клиентов;
дать возможность партнёрам просто и удобно работать с адресами и зонами доставки;
привязаться к понятному формату данных — координатам;
создать универсальный флоу для всех стран, где открыты или открываются наши пиццерии.
Такие изменения повлияют на систему в целом, потому что задевают сервисы «критического пути»: профиль, меню, чекаут — это все те места, где пользователь может ввести адрес. Когда сели придумывать концепцию, пришло осознание, что даже в мобильном приложении нам предстоит грандиозная перестройка. На эти изменения должно реагировать всё приложение. Если где-то будет допущена ошибка, она повлечёт за собой несозданный заказ, грустного или ушедшего клиента и низкий рейтинг в сторах.
В первый заход команда решила разбить работу над новой адресной системой (для краткости мы называем её «Гео») на несколько шагов:
менять адрес можно будет только на старте приложения. Поменять прямо перед оплатой нельзя — иначе придётся обрабатывать смену меню (оно может отличаться в разных пиццериях: может не быть нужных ингредиентов или продуктов);
комментарий к адресу перенесём в корзину: он больше не будет привязан к адресу и его можно будет поменять при каждом заказе. Например, пожелание, чтобы не звонили в дверь, потому что ребёнок спит;
оставляем только один адрес. Раньше можно было добавлять несколько, а сейчас определяем только текущий.
Во второй итерации мы добавили возможность работать с несколькими адресами и редактировать их.
Это легло в список требований для первой версии MVP, и мы принялись реализовывать эту функциональность в нашем приложении.
Техничка
В варианте с Великобританией новый экран появлялся только в одном месте, поэтому мы смогли быстро его интегрировать. С новыми вводными, если посмотреть на схему ниже, можно увидеть, что у нас 4 места, где нужно показывать новые экраны: онбординг, меню, профиль, чекаут. Онбординг — это флоу по выбору страны/города/адреса, который доступен также из меню. А чекаут — экран подтверждения заказа, где пользователь проверяет адрес, способ оплаты, время доставки и оформляет заказ.
![](https://habrastorage.org/getpro/habr/upload_files/045/5cb/695/0455cb6958b77bd0a520207e2a7f23e6.png)
В виде требований это выглядит так:
онбординг: экран ввода адреса и поиска по подсказкам;
меню: список адресов, экран ввод адреса и поиска по подсказкам, экран с картой;
профиль: список адресов, экран ввода адреса и поиска по подсказкам, экран с картой и экран уточнения адреса;
чекаут: экран ввода адреса и поиска по подсказкам, экран с картой и экран уточнения адреса.
Кроме этого, нужно учитывать, что некоторые экраны могут давать разную функциональность пользователю в зависимости от того, где этот экран был вызван, потому что мы даём пользователю возможность добавить адрес в меню и чекауте, но такой возможности нет в профиле.
Некоторые экраны повторяются, так как связаны в один флоу: например, экран с картой может показаться только после показа экрана поиска по подсказкам. Но есть и экран, который показывается только в одном месте по специальному условию: экран уточнения адреса на чекауте.
Адреса после изменений нужно обратно отдавать в то место, откуда вызывали экраны по разным причинам. В меню нужно показать новый адрес, на чекауте перезагрузить меню и сделать проверки на стопы в меню.
Таким образом, есть 4 места для интеграции и похожий флоу; для экранов бывает разная конфигурация, которая влияет на возможности экранов; флоу должен отдавать результат своей работы.
Мы хотели упаковать это решение в один фреймворк с небольшим количеством методов, который сам внутри будет рулить повторяющимся флоу, но так же и давать возможность вызывать какие-то специальные экраны в исключительных случаях.
В текущей архитектуре, как она есть (VIPER), это будет выглядеть очень плохо технически и неудобно потом при поддержке этого кода.
Соединяем требования и архитектуру
В проекте создаём новый фреймворк DeliveryLocation, который будет отвечать за работу с адресами. Вспоминаем наши требования, видим повторяющиеся флоу из нескольких экранов, выкидываем букву R из VIPER и заменяем на C — Coordinator.
Координатор отлично подходит на роль объекта, который рулит повторяющимся флоу из нескольких экранов. Но он не очень подходит для вызова специальных экранов, которые показываются в исключительных случаях. А у нас ещё есть и completions. В результате для разработчиков создаём все доступные методы в координаторе, который может как выполнить целый флоу, так и показать отдельный экран. И в метод можно передать closure для вызова completion — мы не хотим, чтобы наш адресный фреймворк знал что-то про сущности меню или профиля.
В итоге получаем следующий публичный протокол взаимодействия с фреймворком адресов:
public protocol DeliveryLocationCoordinatorProtocol: AnyObject {
func showDeliveryLocation(
on controller: UINavigationController,
configuration: DeliveryLocationScreenConfiguration,
didSelectDeliveryLocationSelectModel: @escaping DidSelectDeliveryLocationSelectModel
)
func showSpecifyAddress(
params: ShowSpecifyAddressParameters
)
}
В некоторых случаях мы отдаём данные для дальнейшей обработки. Например, если клиент выбрал новый адрес на экране меню, то необходимо обновить меню для показа актуальных данных этого адреса. Сам модуль меню обладает такой функциональностью, ему требуется только новый адрес.
Доступ к координатору для других модулей предоставляется через DI. Для каждого модуля создаётся сущность Assembly, которая как затягивает в себя нужные зависимости, так и даёт инструменты для работы другим модулям. Например, есть фреймворк Address, который содержит в себе много бизнес-логики по работе с адресами, но при включении новой адресной системы ему нужна возможность показа новых экранов или новые сервисы, которые лежат в новом модуле.
Тогда класс Assembly может выглядеть так:
AddressAssembly(
dependency: AddressDependency(
…
externalActions: AddressDependency.ExternalActions(
showSpecifyScreenAction: { showSpecifyScreenContext in
…
coordinator?.showSpecifyAddress(params: params)
},
…
)
)
)
Можно увидеть, что в классе Assembly у модуля Address вызывается DeliveryLocationAssembly. Стоит отметить, что классы Assembly хранятся на уровне основного приложения, что позволяет делать модули в изоляции друг от друга, а так же нет зависимостей во время сборки между модулями.
Теперь наше приложение работает так:
Немного про сами карты
![Нативная карта Apple для пользователя. Нативная карта Apple для пользователя.](https://habrastorage.org/getpro/habr/upload_files/f70/5c3/a1e/f705c3a1ec23d1002591f9c7576655bf.png)
Внимательный читатель мог заметить, что мы используем не карты Google или Яндекса, а системные карты, встроенные в iOS. Объясню, почему так решили.
Размер приложения. Не хотелось раздувать приложение, добавляя ещё один фреймворк для карт, а размер у них немаленький (к слову, размер нашего приложения меньше самого фреймворка).
В разных странах разная детализация карт. В одной стране карты Яндекса могут иметь плохую детализацию по сравнению с картами Google, где-то наоборот. Получается, нужно затаскивать оба фреймворка. На эти аргументы можно возразить, что карты Apple тоже не всегда имеют хорошую детализацию. На этот случай у нас есть механизм отрисовки дополнительного слоя карты с хорошей детализацией из открытых источников, что нивелирует проблему.
Цена. Любой сервис с карточным SDK — платный, нам же от этих карт нужны только координаты — работу геокодера выполняет бэк, который в свою очередь обращается к Google API. В варианте с системными картами мы платим только за геокодинг на стороне сервера, на стороне клиента — 0 рублей.
Только нативное решение. Вариант с открытием вебвью влечёт за собой отдельное веселье по обработке JS-ивентов. Если же какой-то SDK работает через вебвью, опять же поднимается вопрос стоимости. Нам от этой карты нужны только координаты, больше ничего.
Ready, steady, go!
Первый запуск произошёл уже через 3 месяца после начала разработки в Великобритании — там мы заменили предыдущий экран с вводом посткода. Эта была отличная возможность испытать в бою наш новый подход. У нас были только экраны подсказок и карты, мы заблокировали возможность добавлять или редактировать адреса. Пользователь мог работать только с одним адресом. Но даже этого было достаточно для запуска: люди смогли привычно пользоваться адресной системой, которая им знакома по другим приложениям, а партнёрам не нужно было заводить кучу почтовых индексов для каждого адреса.
После получения первого фидбэка и списка багов мы вернулись к нашей схеме декомпозиции и продолжили работу над более совершенным вариантом с возможностью редактировать адреса и сохранять их в профиле. Ещё через 3 месяца первая полноценная версия увидела свет (также в Великобритании), а теперь она раскатана на все страны, где есть Додо Пицца. Параллельно вносим некоторые изменения и улучшаем флоу.
Много внешних и внутренних обстоятельств повлияли на скорость запуска, но не остановили нас, но это другая история.
Сейчас рабочая функциональность получена и запущена везде. Немалую роль в этом сыграли как юнит-тесты, так и E2E тесты, которые из раза в раз гарантировали нам работоспособность как критического флоу приложения, так и нашей функциональности.
Доделали ли мы гео? Нет. Впереди большая работа над переосмыслением UX в приложении, и мы обязательно докрутим наше решение до идеала.
Хотите больше знать о том, как мы разрабатываем мобильные приложения Додо Пиццы, Дринкит и Донер 42 — подписывайтесь на канал Dodo Mobile.