Site icon AppTractor

Водитель приедет через 3 минуты: реализация Uber Live Activity на iOS

Введение

На ключевой презентации WWDC 2022 года компания Apple преподнесла неожиданный сюрприз, представив новую функцию Live Activities на примере приложения Uber Rider. Этот анонс вызвал ажиотаж по поводу грядущей функции и положил начало захватывающему путешествию нашей команды.

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

Планирование предстоящей работы

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

В компании такого размера, как Uber, работа обычно планируется на полгода вперед, а ресурсы и сроки всегда ограничены.  Поэтому, обдумывая приоритетность этой функции, мы признали потенциальную выгоду от появления в App Store в первый же день, чтобы стать чемпионом платформы.  Мы также считали, что эта функция принесет огромную пользу нашим пользователям, поскольку теперь они смогут наблюдать за обновлениями поездок в подробном виде за пределами приложения.

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

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

UI с первого взгляда

С точки зрения UX мы быстро осознали уникальную задачу, которую ставят перед нами Live Activities. Информация на этой поверхности просматривается всего несколько секунд за раз, и она выходит за пределы основного приложения. Попытка впихнуть информацию из нашего приложения в представление Live Activity была бы ошибкой. Пользователи будут просматривать ее лишь в течение короткого времени, поэтому мы распределили информацию по категориям, основываясь на приоритетах, которые, по нашему мнению, будут у пользователей во время поездки:

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

Рисунок 1. Визуальное представление иерархии информации на Live Activity.

Работа с Dynamic Island — вырезом в форме таблетки, отображающим информацию о приложении в верхней части экрана, — позволила довести эту концепцию до крайности, поскольку она занимает очень мало места. В отличие от отдельной Live Activity, Dynamic Island делит свою ограниченную площадь с другими приложениями и системной информацией. Это означает, что наш дизайн должен быть еще более лаконичным и эффективным. Мы должны были сделать так, чтобы информация была понятной и полезной на крошечном пространстве, не перегружая пользователя и не пересекаясь с другими данными. Например, для фазы поездки мы провели несколько итераций, чтобы убедиться, что время высадки не будет перепутано с системными часами. Каждый пиксель на счету, и нам пришлось проявить исключительную изобретательность, чтобы максимально использовать эту функцию.

Рисунок 2: Все этапы поездки, выраженные в динамическом острове.

Преодоление технических трудностей

Live Activity разработана как отдельная от основного приложения цель, не имеющая доступа к сетевым возможностям и состоянию. Они обновляются через основное приложение или push-уведомления. После вызова Live Activity создает собственное представление SwiftUI и оно существует без возможности прямого взаимодействия с основной целью. При такой настройке возникла серьезная проблема: как обрабатывать изображения, динамически необходимые Live Activity, например, фото водителя и автомобиля.

Чтобы решить эту проблему, мы использовали две технические особенности приложения: главная цель находится в фоновом режиме во время запроса поездки и App Groups позволяет целям читать и записывать в общую директорию. При обновлении полезной нагрузки мы обрабатываем все найденные URL-адреса изображений и сериализуем их на диск. Затем Live Activity считывает эти же URL с диска.

Рисунок 3: Временная шкала обновления Live Activity и стратегия кэширования ассетов.

То, что обычно является простой задачей загрузки ресурсов и представления их на экране, в случае с Live Activity становится более сложным процессом, который добавляет несколько возможных точек отказа. Единственный плюс в нашей ситуации заключается в том, что, поскольку ресурсы редко меняются во время поездки, у нас есть возможность повторить попытку при каждом обновлении полезной нагрузки, если это необходимо.

Позже в этой статье мы обсудим возможные альтернативные решения, но есть один главный аспект, который стоит отметить заранее: приложение не гарантированно будет работать в фоновом режиме во время поездки. Приложение Rider использует право на работу в фоновом режиме, поэтому мы можем обновлять местоположение пользователя и гарантировать точную информацию о подборе, но пользователь или ОС все равно могут убить приложение, прекратив поток обновлений.

В нашем первом проекте мы полагались исключительно на приложение, работающее в фоновом режиме, для обновления живой активности, но во время бета-тестирования мы получили сообщения от пользователей, которые жаловались, что Live Activity перестала обновляться или не удалялась после завершения поездки. Поэтому мы выбрали другой дизайн, использующий внутренний сервис и Push-уведомления.

Основная часть первого решения заключалась в оценке влияния этой функции на API бэкенда.  Хотя эта функция увеличит трафик наших API-сервисов, мы полагали, что пользователи будут проводить меньше времени внутри приложения, одновременно снижая трафик (приложению необходимо часто получать данные, находясь на переднем плане). Мы не могли предположить, как изменится поведение наших пользователей, но, по нашим оценкам, переход нагрузки с переднего плана на фоновый режим не окажет существенного влияния. Кроме того, в качестве первоначальной меры предосторожности был добавлен фиче флаг для контроля скорости извлечения данных в фоновом режиме. Однако после перехода на сервис OOA (читайте дальше) скорость обновления уже контролируется бэкендом, который имеет более сложную логику балансировки нагрузки и дросселирования.

Измерение влияния и сбор данных

Измерение влияния новых функций — стандартная практика Uber. Мы ожидали, что Live Activities изменят взаимодействие пользователей с нашим приложением и приведет к значительным улучшениям в таких показателях, как сокращение количества отмен и времени ожидания в пункте назначения. Приведенные ниже цифры могут показаться небольшими, но на самом деле это огромные победы в масштабах нашей компании. Судя по результатам, мы считаем, что пассажиры стали лучше понимать, когда их заберут, что привело к увеличению числа завершенных поездок (и доходов водителей).

Приведенные выше метрики являются показателями поведения пользователей, которые измеряются независимо от функций, добавляемых в приложение. Когда дело дошло до сбора инженерных показателей для Live Activities, их уникальное поведение усложнило задачу. В отличие от обычного приложения, Live Activities не предлагают простых способов сбора показателей без существенных пробелов. Нам приходилось делать предположения на основе неполных данных, например, отслеживать последовательность activityStateUpdates, чтобы убедиться, что Live Activities правильно очищаются по окончании поездки. Однако эта последовательность выдается только в том случае, если приложение работает в фоновом режиме, что не гарантируется, а значит, значительное количество событий может быть пропущено.

Чтобы смягчить проблему, нам пришлось объединить данные о событиях переднего плана, в которых присутствует Live Activity, но при этом не происходит поездка. Этот метод не гарантирует 100-процентного покрытия случаев, но он позволяет нам установить приемлемый базовый уровень и создать метрики регрессии и панели мониторинга, которые, хотя и не отражают полной картины, все же полезны для выявления регрессий в производстве или во время разработки.

Внедрение флагов функций

Еще одна стандартная практика Uber — отмечать новые функции или значительные изменения в итерации. В случае с Live Activities это было непросто из-за отсутствия доступа к сети и двусторонней связи с основной целью. Мы используем тот же трюк, что и при загрузке изображений: при запуске основной цели мы считываем флаги функций из нашего стандартного конвейера и записываем их значения на диск. Затем Live Activity считывает эти значения с диска при вызове.

DSL, Android и служба OOA

Хотя в Android нет прямого аналога Live Activities, мы все же можем усовершенствовать стратегию push-уведомлений, обновляя их, а не отправляя новые, добавляя тот же визуальный элемент, что и в Live Activity, чтобы добиться паритета функций.

Чтобы упростить этот процесс и обеспечить согласованность в iOS и Android, мы разработали простой server-driven язык. Этот язык позволяет нам легко изменять содержимое, представленное как в Live Activity, так и в Push-уведомлениях для Android. Благодаря централизации логики контента на сервере мы можем динамически обновлять и адаптировать пользовательский опыт, не требуя обновления приложений. Такая система обеспечивает гибкость и гарантирует, что любые изменения или новые функции могут быть быстро реализованы на обеих платформах, поддерживая единый опыт для всех наших пользователей.

Нам нужно было разработать domain specific язык (DSL), который имел бы очень небольшой объем реализации и мало внешних зависимостей. В Uber уже есть система пользовательского интерфейса, управляемая сервером, но она довольно обширна, и поэтому ее нельзя импортировать во внешний объект без ущерба для размера бинарного файла. Кроме того, сведение возможностей к минимуму позволяет сократить расходы на обслуживание в будущем.

Нашим решением стало создание конкретного полуописательного DSL. В него включены только те элементы пользовательского интерфейса, которые могут присутствовать в Live Activities (например, никаких текстовых полей или сегментированных контроллеров), и не было предусмотрено никакой обширной стилизации. Для представлений, нуждающихся в стилизации, мы предоставляем массив тегов, который затем обрабатывается на уровне клиента. Например, метка заголовка может быть представлена компонентом типа Label, а также текстом и тегом типа title. Затем клиент применяет шрифт, цвет, количество строк, выравнивание и т. д. для конкретного стиля заголовка.

Рисунок 5: Пример полезной DSL нагрузки, которая представляет Live Activity.

Наконец, DSL не будет представлять никакой информации о положении на экране. Для заголовка или прогресс-бара может быть предоставлен другой вид, но расположение каждого вида задается клиентом и не может меняться (за исключением инструкций, которые применяются с помощью вышеупомянутых тегов).

Рисунок 6: Демонстрация работы шаблонов на Live Activity и Android Push Notification.

В тандеме с новой DSL-системой мы разработали бэкэнд-сервис, специально предназначенный для поверхностей, которые живут за пределами приложения; мы назвали его OOA Service (Out Of App). OOA Service отвечает за логику балансировки количества обновлений, доставляемых в службу Apple Push Notifications Service. Она оценивает, являются ли изменения состояния приложения достаточно важными, чтобы быть доставленными, а также отменяет изменения состояния, которые происходят в быстрой последовательности. Из-за необходимости оценки и дебаггинга эта служба должна кэшировать предыдущие состояния для всех одновременных заходов на платформе, что требует значительных усилий по масштабированию.

Создание DSL и сервиса OOA стало значительным шагом вперед. Это не только упростило процесс разработки, но и открыло возможности для интеграции изменений без необходимости дублировать логику дерева решений и развертывать код на нескольких платформах. Построив это типовое решение, мы избежали необходимости решать сложные проблемы несколько раз в разных командах. В частности, это решение, которое, по нашему мнению, принесет дивиденды в будущем, когда все больше и больше вертикальных команд Uber будут использовать поток Live Activity.

Даже платформа Eats уже использует значительную часть мобильного фреймворка, который занимается жизненным циклом Live Activity, кэшированием изображений и внедрением флагов функций. Они также оценивают возможность внедрения DSL и OOA Service после того, как увидели положительные результаты интеграции с Rider.

Заключение

Создание Live Activity для приложения Rider на iOS было напряженным, но полезным путешествием. От неожиданного анонса на WWDC до сложностей разработки и внедрения новой технологии в сжатые сроки, этот опыт продемонстрировал стойкость, адаптивность и креативность нашей команды. Мы преодолели технические препятствия, пересмотрели подход к UX и в итоге создали функцию, которая, по нашему мнению, улучшила впечатления водителей и пассажиров. Я надеюсь, что частички изобретательности, которые мы продемонстрировали в этой статье, могут вдохновить любого разработчика, работающего над Live Activities, и помочь ему преодолеть аналогичные сценарии и в целом использовать прагматичный подход к опыту, который живет за пределами основного приложения.

В личном плане это был отличный опыт и возможность: разработка продукта для раннего внедрения в компании нашего масштаба — сложная, но безумно увлекательная задача.

Источник

Еще про Live Activity

Exit mobile version