Использование Jetpack Compose в продакшне: первые впечатления

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Мы в МТС Банке давно ждали релиза Jetpack Compose, чтобы использовать его в продакшне. В прошлом месяце такая возможность наконец появилась — мы решили обновить дизайн одного из экранов нашего приложения «МТС Банк для бизнеса» для Android.



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

Совместимость

В архитектуре нашего приложения используется Single-Activity подход, поэтому, как описано в документации, нам нужно было всего лишь заменить реализацию метода onCreateView() у фрагмента, чтобы вернуть ComposeView, который описывает UI экрана.



Сразу же заметили одну особенность — первый запуск этого экрана стал занимать значительно больше времени (больше одной секунды!). При поиске причин была найдена отличная статья, в которой описаны результаты исследований, показывающие, что отключенная оптимизация R8 и включенная возможность дебага сборки добавляют по 0,5 секунды ко времени первой загрузки экрана с Compose в приложении. Кроме того, первый экран с Compose в приложении грузится примерно в два раза дольше, чем остальные.

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

Про совместимость можно почитать подробнее в официальной документации.

Интеграция с другими библиотеками Jetpack

Внутри @Composable функций можно получить доступ к ViewModel напрямую, используя функцию viewModel() из библиотеки lifecycle-viewmodel-compose (пока в альфе). Современные фреймворки для DI (Koin, Dagger/Hilt) также предоставляют такую возможность. Однако, так как мы не вносим изменения в граф навигации и отображаем наш экран внутри фрагмента (отчасти для того, чтобы изменения были атомарными и не влияли на остальные части приложения), более простым решением показалось продолжить использовать ViewModel, сохраняя ее по-старому — как переменную во фрагменте.

Тут можно почитать официальные рекомендации по интеграции с другими библиотеками.

Material design



Из-за небольшого размера нашего эксперимента сложно выделить что-то особенное на эту тему. Пожалуй, стоит отметить приложение Material Design catalog, которое точно будет полезным, чтобы понять, какие элементы уже реализованы и готовы к использованию в Jetpack Compose.

Отличным источником информации также послужат официальные примеры приложений, которые частично или полностью написаны на Compose, в этом репозитории на GitHub.

Списки

Если вы видели хотя бы пару видео про Compose, то скорее всего уже отметили, насколько удобная и простая в использовании получилась замена для RecyclerView (LazyList):



Также поведение списка очень легко кастомизировать: один из элементов нового дизайна — ViewPager c двумя группами кнопок. Однако на момент написания этой статьи в Compose все еще нет официальной версии ViewPager. Решением этой проблемы может послужить отличная библиотека от Chris Banes, в которой он реализован, но его API отмечен как экспериментальный (кстати, библиотека также используется в официальных примерах и, возможно, в будущем станет частью Compose). Это вполне подойдет для pet-проекта или некоторых приложений, но использование альфа-версий библиотек и экспериментальных API может быть не самой лучшей идеей для банковского приложения.

Хорошим решением может стать использование существующего ViewPager с помощью @Composable AndroidApi. В качестве альтернативы можно было бы использовать LazyRow и какой-либо аналог SnapHelper для RecyclerView. В Compose на данный момент такого аналога нет, но реализовать его самостоятельно намного проще, чем кажется: всего лишь нужно передать кастомную реализацию FlingBehavior в LazyRow. Пример такой реализации можно посмотреть здесь.

После синхронизации Row, в котором добавлены кнопки управления, с состоянием LazyRow через LazyListState конечный результат выглядит следующим образом:

Также множество хороших примеров с открытым кодом (и не только по спискам) можно найти здесь.

Анимации

Наверное, это одна из моих любимых возможностей Jetpack Compose, новый API для анимации невероятно прост и удобен в использовании. Можно получить хорошее представление о нем, посмотрев короткое видео с примерами. За пять минут можно научиться использовать анимации, которые подойдут для большинства случаев. Да, анимации стали настолько удобными!

Лучшие практики для @Composable функций

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

Библиотечные функции написаны именно в таком порядке. Давайте разберем, почему порядок важен, на примере функции, которую мы написали для своей реализации CollapsingToolbarLayout:





Указание модификатора (Modifier) в начале или после обязательных параметров позволяет не указывать название аргумента в местах вызова:



Указывая параметр с содержанием (content) на последнем месте, мы сможем использовать лямбда-выражения.

Примеры из стандартной библиотеки: любая @Composable функция.

Используйте вспомогательные классы для группировки связанных параметров.

В примере выше используются два таких класса:



Использование таких вспомогательных классов позволяет сгруппировать параметры нашего тулбара и улучшает чтение (и написание) кода при его создании.

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

Примеры из стандартной библиотеки: TextStyle, PaddingValues, SwitchDefaults.

Создавайте две версии @Composable функций: с состоянием и без (stateful and stateless)

Если функция будет хранить какое-либо состояние, хорошо предусмотреть возможность управления этим состоянием извне. Для этого можно использовать state hoisting (в Compose: паттерн, который заключается в «поднятии» состояния, чтобы оно могло быть управляемым в месте вызова). У LazyRow и LazyColumn состояние задано как параметр функции с значением по умолчанию, соответственно, при вызове этой функции мы можем получить его и изменить при необходимости. В нашем примере мы сделали то же самое — внутри функции параметр scrollState передается с помощью Modifier.verticalScroll (scrollState) в Column, в котором расположено содержимое (content). На экране выше нет необходимости управлять этим состоянием, но так как его в любом случае необходимо инициализировать внутри функции (для согласования скролла содержания и размера тулбара), то нет ничего плохого в том, чтобы объявить его в качестве входного параметра. Это, напротив, сделает нашу функцию более гибкой.

Также стоит отметить, что общепринято называть функции, которые служат для хранения состояния, используя префикс “remember”. Например: rememberLazyListState() и rememberCoroutineScope().

Используйте скоуп содержимого в качестве приемника (receiver) для функций, отображающих содержимое (content)

В Compose модификаторы представляют собой функции расширения, и некоторые из них доступны только в скоупе соответствующих @Composable (BoxScope, ColumnScope и т.д.). Например, в ColumnScope модификатор align():



Возвращаясь к нашему примеру, входной параметр content объявлен как:


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



Примеры из стандартной библиотеки: любая @Composable функция с содержимым.

Избегайте дублирования кода.

В Compose акроним DRY (don’t repeat yourself) стал еще более актуальным. Конечно, в xml можно было использовать тэг и даже передать параметры с помощью DataBinding, а иногда удобнее и быстрее собрать новый компонент через copy/paste разметки xml похожих экранов (или элементов). Сейчас, когда UI переезжает в @Composable функции, в которых можно использовать всю силу Котлина, переиспользовать код проще, чем когда-либо. Только, пожалуйста, не забывайте о горячих клавишах Android Studio для этого (command+option+m или ctrl+alt+m для Mac/Windows).

Заключение

В нашем приложении мы используем архитектурный подход с MVI, в котором состояние едино, иммутабельно и данные двигаются в одном направлении (UDF — unidirectional data flow), поэтому бизнес-логика в ViewModel осталась практически нетронутой, и в этом плане внедрение Compose было достаточно простым. Одно из немногих опасений, которые еще остаются, заключается в том, что некоторые API фреймворка все еще экспериментальные (sticky headers для списков, отсутствие готового, «из коробки» ViewPager, отсутствие готовых анимаций для изменений в списках и т.д.). При использовании Jetpack Compose вам скорее всего придется столкнуться с тем, что некоторых привычных вещей в нем пока нет, так что придется писать свою реализацию или использовать API для включения существующих View (WebView и MapView — два самых популярных примера). При этом, учитывая все преимущества, которые мы получаем, очевидно, что стоит начать знакомство с Compose как можно скорее.

Авторы:

Роман Камышников, ведущий разработчик Android Центра компетенций мобильной разработки МТС Банка

Арсений Сафин, руководитель центра компетенций мобильной разработки МТС Банка

Сергей Кривопиша, технический лидер Android Центра компетенций мобильной разработки МТС Банка
Источник: https://habr.com/ru/company/ru_mts/blog/588096/


Интересные статьи

Интересные статьи

В качестве среды рабочего стола я предпочитаю KDE. KDE надежна, настраиваема всевозможными способами, и к тому же замечательно работает. Она относится к вам как к ответст...
В данной статье я хочу описать известные мне способы встраивания mapbox-gl в React приложение, на примере создания простого веб приложения содержащего карту на Next.js с ...
Привет, Хабр. С момента появления Raspberry Pi 4 стало значительно больше желающих использовать этот микрокомпьютер в качестве основного ПК. Вычислительная мощность Pi4 стала уже весьм...
Написанию этого небольшого руководства предшествовало нескольких недель мучений с попытками работы над проектами, когда было необходимо чтобы был запущен контейнер с сайтом для работы...
На сегодняшний день у сервиса «Битрикс24» нет сотен гигабит трафика, нет огромного парка серверов (хотя и существующих, конечно, немало). Но для многих клиентов он является основным инструментом ...