Разработка
Погружение в Compose — уроки создания Maps Compose
В этой статье я хотел бы рассказать об истории создания Maps Compose, о том, как она появилась, и о некоторых уроках, извлеченных во время работы над библиотекой.
В этом году мы выпустили Maps Compose, библиотеку Jetpack Compose для добавления Google Maps в ваши приложения. По своей сути Maps Compose — это библиотека взаимодействия с Maps SDK для Android, которая предоставляет дружественные к Compose API. Таким образом, для создания composable элементов Maps Compose нам пришлось работать с ограничениями существующего API Maps SDK и доступных функций интероперабельности Compose.
Maps Compose прошел через несколько итераций дизайна и реализации до того, как был выпущен первоначальный релиз. В этой статье я хотел бы рассказать об истории создания Maps Compose, о том, как она появилась, и о некоторых уроках, извлеченных во время работы над библиотекой. Этот пост особенно актуален для разработчиков SDK, желающих обеспечить поддержку Compose. Тем не менее, он также актуален просто для любознательных читателей. Если вы попали в одну из этих выборок — читайте дальше!
Бэкграунд
До того, как Maps Compose стал доступен, использование Maps SDK в Compose уже было возможно благодаря API-интерфейсам совместимости Compose, а именно composable AndroidView. Простые варианты использования — например, отображение карты с одним маркером на ней — были легкими. Однако умеренно сложные интеграции с большим количеством настроек и рисунков требовали написания большого количества промежуточного кода, что делало использование не таким простым.
Чтобы оценить, насколько полезной может быть библиотека Compose для разработчиков Android, я написал в Твиттере следующее:
What if adding a Google Map in #Jetpack #Compose looked like this? pic.twitter.com/c9IOeXmcog
— Chris Arriola (@arriolachris) December 22, 2021
К моему удивлению, многие разработчики ответили такими утверждениями, как «Да, пожалуйста!», «Круто!» и «Это заменит несколько сотен строк кода». Как один из инженеров по связям с разработчиками, работавшим в то время над Maps SDK, я уделил приоритетное внимание выпуску composable элементов для Maps SDK. Внутри я предложил дизайн и реализацию для Maps Compose и поработал с Адамом Пауэллом, Леландом Ричардсоном и Беном Тренгроувом, чтобы довести библиотеку до финишной черты.
Предоставление обратной связи имеет значение и приводит к реальным изменениям продукта. Мы рады слышать вас.
Уроки
Урок 1. Используем классы основного Maps SDK
Maps SDK существует уже более 10 лет и используется во многих приложениях. Хотя Compose представляет совершенно другой способ создания пользовательского интерфейса, composable версия карт все равно должна ощущаться знакомой. Если вы немного знакомы с Compose и раньше использовали Maps SDK, компонуемые карты должны быть для вас интуитивно понятны.
Чтобы сделать API интуитивно понятным, мы по возможности повторно использовали базовые классы Maps SDK. Например, для обновления камеры в Maps Compose можно использовать существующий класс CameraUpdate, созданный на основе объекта CameraUpdateFactory.
Однако были случаи, когда существующие классы в Maps SDK нельзя было использовать как есть. Например, повторное использование класса UiSettings не имеет смысла, поскольку его можно получить только после создания карты. В идеале вы должны иметь возможность создать экземпляр этого класса и передать его в составной объект GoogleMap. Чтобы обойти это, класс был зеркально отражен в типе Maps Compose, MapUiSettings. Именование близко соответствует существующему классу UiSettings с дополнительным префиксом «Map», чтобы упростить обнаружение API. MapUiSettings имеет те же самые свойства и значения по умолчанию, что и UiSettings, с той разницей, что его можно создать и передать в composable объект GoogleMap, а не получить и изменить его из другой точки:
Существуют и другие свойства карты, которые можно изменить во время выполнения (например, setBuildingsEnabled(boolean)). Одним из соображений, которые у нас были, было представление этих свойств как отдельных параметров в составном объекте GoogleMap. Однако это значительно увеличило бы количество параметров, поскольку есть много свойств, которые можно изменять. Вместо этого мы решили создать отдельный класс MapProperties, который содержит следующие свойства, настраиваемые в рантайме:
Для объектов, которые можно нарисовать на карте (маркеры, полилинии и т.д.), императивный подход на основе View заключается в вызове методов add*, таких как GoogleMap.addMarker(MarkerOptions), для объекта GoogleMap. Чтобы преобразовать это в Compose, composable объект GoogleMap должен принимать список в параметре для каждого рисунка, однако этот API может быть сложно использовать для интеграций, содержащих множество нарисованных объектов со сложной логикой. Вместо этого мы решили предоставить дружественный к Compose API — общую лямбду контента, в которой рисунки можно вызывать как отдельные composable.
Нужно нарисовать на карте маркер, полилинию или другой поддерживаемый рисованный объект? Вызовите composable функцию Marker, Polyline или другой декоратор в лямбда-выражении содержимого составного объекта GoogleMap следующим образом:
Урок № 2: Используем преимущества Kotlin
Maps SDK был создан еще до того, как Kotlin стал предпочитаемым языком для написания приложений для Android. Таким образом, Maps SDK в основном построен на Java. Jetpack Compose, с другой стороны, полностью написан на Kotlin и в значительной степени опирается на идиомы Kotlin. Для Maps Compose мы также решили использовать функции языка Kotlin, такие как корутины, чтобы раскрыть идиоматический API Kotlin.
Например, Compose использует функции приостановки корутин Kotlin для API анимации. Таким образом, имело смысл предоставить аналогичный API для callback-based API в Maps SDK. Например, анимацию камеры и ожидание ее завершения можно выполнить в скоупе корутины:
Список других идиом Kotlin, широко используемых в Compose, см. в Kotlin для Jetpack Compose.
Урок № 3. Обеспечиваем согласованность с другими API Compose
Поддержание согласованности с другими API-интерфейсами набора инструментов Compose обеспечивает хорошую эргономику разработчика. Это упрощает использование функций и, следовательно, ускоряет разработку, поскольку следует знакомому соглашению с другими API. Независимо от того, являетесь ли вы разработчиком библиотеки или приложения, эта согласованность необходима для обеспечения простоты использования. Гайдлайны Compose API — отличный ресурс для изучения соглашений, которым следуют API Compose.
Вот несколько шаблонов, описанных в рекомендациях, принятых в Maps Compose:
- Composable объект GoogleMap соответствует требованиям Elements принимать и соблюдать параметр модификатора.
- Компонуемый MarkerInfoWindow соответствует такому пункту, указанному в разделе Compose UI Layouts: «Функции макета должны помещать свой основной или наиболее распространенный параметр @Composable функции [это должно быть названо контентом] в последнюю позицию, чтобы разрешить использование завершающего лямбда-синтаксиса Kotlin».
Было несколько случаев, когда мои первоначальные проекты отличались от этих рекомендаций, и я счел очень полезным обратиться к ним, чтобы скорректировать решения в отношении API, чтобы они лучше соответствовали рекомендациям Compose.
Урок № 4. Простые классы лучше всего подходят для бинарной совместимости
Классы данных Kotlin — это эффективный способ хранения данных. Они предлагают несколько сгенерированных методов, которые вам не нужно писать самостоятельно, что позволяет сэкономить несколько строк кода на класс. Однако, если вы пишете библиотеку, классы данных имеют скрытую стоимость, поскольку будущие изменения в классе данных нарушают двоичную совместимость. Добавление новых свойств изменит сгенерированную сигнатуру метода для copy(), и в зависимости от того, где было добавлено новое свойство, оно также может нарушить функции деструктурирования, что приведет к сбою у потребителей. Чтобы избежать этого, Maps Compose использует простые классы для MapUiSetting и MapProperties. Спасибо за совет Джейку Уортону, он указал на это в своем сообщении в блоге Kotlin.
Урок № 5: Используем общие типы Compose
Чтобы настроить цвета нарисованного объекта в Maps SDK, вы предоставляете ему целое число цвета. Например, чтобы настроить цвет заливки круга, вы указываете целое число цвета при создании объекта CircleOptions. Maps Compose Circle, с другой стороны, вместо этого использует предоставленный Compose класс Color.
Одной из замечательных функций Compose является встроенная поддержка применения материальных тем к вашему приложению. Таким образом, используя предоставленный Compose класс Color, когда GoogleMap и его дочерние composables используются в MaterialTheme, цвета автоматически адаптируются к цветам системы в светлом или темном режиме.
Урок № 6: Subcomposition — это мощное средство
В начале процесса разработки мы определили, что управление украшениями карты с течением времени с помощью API эффектов в соответствии с моделью данных приложения было утомительным и подверженным ошибкам. Необходимость управлять деревом элементов — это фактически та же проблема, что и управление компонуемым деревом UI, поэтому лучшим решением было бы использование одного и того же базового инструментария для непосредственного обновления элементов по мере изменения состояния с течением времени. Этот подход оказался намного более простым и интуитивно понятным по сравнению с использованием побочных эффектов.
Для этого мы использовали класс Applier и composable ComposeNode для поддержки дочернего API (лямбда контента) добавления нарисованных объектов (маркеров, полилиний, полигонов и т.д.) на карту. Полученная реализация создает новую субкомпозицию, которая управляет состоянием карты вместо нодов Compose UI.
Взяв в качестве примера маркер, с помощью субкомпозиции мы можем гарантировать, что состояние карты будет обновляться по мере того, как происходит изменение. Так, например, если composable объект Marker ранее был в композиции, а затем был удален, мы можем использовать соответствующий метод удаления узла, чтобы убедиться, что базовый объект Marker Maps SDK также удален с карты.
Если вы хотите углубиться в код, ознакомьтесь с реализацией MapApplier и Marker, чтобы узнать больше о том, как использовать эти API.
Вывод
В целом, я был впечатлен тем, как доступные API интероперабельности Compose сделали поддержку Maps SDK в Compose выполнимой. В то время как нативная реализация Maps SDK все еще желательна, бридж Maps Compose устраняет пробел для многих разработчиков Maps, использующих Compose.
Надеюсь, вы нашли этот пост полезным и узнали кое-что о разработке Compose API и обеспечении работы Compose с существующим кодом View.
Если вы новичок в Compose или Maps Compose, ознакомьтесь с примерами приложений, чтобы узнать больше:
- Примеры Compose
- Примеры Maps Compose
- Курс «Основы Android с Compose»
- 10 открытых проектов Jetpack Compose, которые вас вдохновят