Привет, я Михаил Селезнев, андроид-разработчик в компании 1221Systems. Поделюсь своими впечатлениями о Compose — будет интересно коллегам, которые думают о переходе на него. Расскажу, в чем были сложности и что помогло мне быстрее разобраться.
Пару слов о моем бэкграунде: я в профессии больше четырех лет, в 1221Systems пришел полтора года назад на проект по разработке большого приложения для заказа продуктов ритейлинговой сети. Проработал около года, в марте этого года перешел на другой проект — приложение для сотрудников компании.
Само приложение написано на достаточно современном стеке: MVI, Koin, Coroutines + Flow, Jetpack Compose. При этом навигация реализована на фрагментах. В onCreateView возвращаем ComposeView, где в setContent прокидываем composable функцию.
abstract class ComposeFragment : Fragment() {
private var composeView: ComposeView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = ComposeView(requireContext()).also { composeView = it }
override fun onDestroyView() {
super.onDestroyView()
composeView = null
}
protected fun setContent(content: @Composable () -> Unit) {
composeView?.setContent {
AppTheme(content = content)
}
}
}
Никаких сторонних библиотек для навигации мы не используем, все транзакции проводим вручную.
Я пришел уже на готовый проект. И до этого с Compose, практически, не был знаком. Только слышал про него на конференциях и читал какие-то статьи на эту тему.
Первая фича: удаление аккаунта
Первая фича, которую мне нужно было реализовать — это удаление аккаунта: один экран с пояснительным текстом и двумя кнопками, прибитыми к низу экрана.
Для начала изучил дизайн и требования, а уже после приступил к реализации.
Создал фрагмент и сделал на него переход: тут никаких сложностей не возникло. Дальше UI — решил начать с кнопок. Так как приложение уже в проде, то я решил, что такие кнопки точно уже где-то используются в других местах, поэтому пошел искать их через Resource Manager.
В случае с View это очень удобный инструмент, который позволяет быстро просмотреть все layout’ы в проекте. Но в случае с Compose он не работает: Resource Manager просто не видит СomposeView.
Начал искать другие варианты и вспомнил про Layout Inspector (далее LI), в котором можно посмотреть, насколько эффективно работает твой UI. Открыл в приложении другой экран с нужной кнопкой и через LI попал в composable функцию, описывающую кнопку.
Конечно, можно было пойти просто по файлам и найти что-то по названию, но на тот момент я еще плохо знал проект, и не хотел тратить много времени на поиск.
Добавил первую кнопку, запустил приложение — кнопка показалась. Появилось первое чувство радости за себя - что-то получилось. Нашел вторую кнопку, добавил, запустил приложение и вот тут уже первое разочарование — кнопки наложились друг на друга.
OutlinedRedButton(
text = "Удалить аккаунт",
onClick = {}
)
GreenButton(
text = "Назад",
onClick = {}
)
В классическом View вопрос решился бы с помощью Constraint Layout, но у нас Compose. Поэтому я пошел читать официальную документацию и обнаружил, что для вертикального расположения элементов, нужно использовать элемент Column. Посмотрел, что он используется также на других экранах и добавил его себе. Перезапустил приложение, и о чудо, кнопки отобразились друг под другом.
Column {
OutlinedRedButton(
text = "Удалить аккаунт",
onClick = { }
)
GreenButton(
text = "Назад",
onClick = {}
)
}
Дальше через LI нашел похожий по стилю текст и добавил на свой экран.
Почти то, что нужно! Только кнопки должны быть прибиты к низу экрана.
Тут оказалось все достаточно просто: можно использовать элемент Spacer, который заполнит все пространство между текстом и кнопкой. Дальше дело оставалось за малым: через modifier’ы добавил отступы и получил нужный результат.
Стоит также отметить, что все перечисленное выше было обернуто в элемент Scaffold, который позволяет удобно отобразить TopAppBar. Итоговый код получился таким:
@Composable
fun DeleteAccountBody(
modifier: Modifier = Modifier,
onBackClick: () -> Unit = {}
) {
Scaffold(
modifier = modifier,
topBar = {
Toolbar(
title = "Управление профилем",
onBackClick = onBackClick
)
}
) { paddingValues ->
Column(
modifier = modifier
.padding(paddingValues)
.padding(horizontal = 16.dp)
.fillMaxSize()
) {
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Удаление аккаунта",
style = AppTheme.typography.h3,
color = AppTheme.colors.redPrimary
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Здесь ты можешь удалить свой аккаунт",
style = AppTheme.typography.subhead,
color = AppTheme.colors.textSecondary
)
Text(
text = "После удаления данные будут утеряны",
style = AppTheme.typography.subhead,
color = AppTheme.colors.textSecondary
)
Spacer(modifier = Modifier.weight(1f))
OutlinedRedButton(
modifier = Modifier.fillMaxWidth(),
text = "Удалить аккаунт",
onClick = { }
)
Spacer(modifier = Modifier.height(16.dp))
GreenButton(
modifier = Modifier.fillMaxWidth(),
text = "Назад",
onClick = {}
)
Spacer(modifier = Modifier.height(46.dp))
}
}
}
Вторая фича: динамичный список элементов
Следующая фича была поинтереснее - это был экран оценки. Помимо текста и кнопки добавился динамический список элементов, которые приходят с бэка.
В классическом view для реализации такого элемента нужно добавить ресайклер, адаптер, делегат, отрисовать в xml view. В Compose этого всего делать не нужно! Достаточно взять Chip-виджет, который входит в библиотеку Compose Material, во ViewModel получить список элементов, прокинуть на Ui слой и пройти список в цикле. Все! На отрисовку этого элемента я потратил в несколько раз меньше времени, чем если бы делал это на View.
Но и это еще не все. Так как элементы в списке можно выбрать, то хочется, чтобы заливка цветом происходила красиво. И Compose позволяет это сделать буквально парой строчек кода с помощью функции animateColorAsState.
Итоговый код:
@Composable
public fun FlowRow(
modifier: Modifier = Modifier,
mainAxisSize: SizeMode = SizeMode.Wrap,
mainAxisAlignment: FlowMainAxisAlignment = FlowMainAxisAlignment.Start,
mainAxisSpacing: Dp = 0.dp,
crossAxisAlignment: FlowCrossAxisAlignment = FlowCrossAxisAlignment.Start,
crossAxisSpacing: Dp = 0.dp,
lastLineMainAxisAlignment: FlowMainAxisAlignment = mainAxisAlignment,
content: @Composable () -> Unit
) {
Flow(
modifier = modifier,
orientation = LayoutOrientation.Horizontal,
mainAxisSize = mainAxisSize,
mainAxisAlignment = mainAxisAlignment,
mainAxisSpacing = mainAxisSpacing,
crossAxisAlignment = crossAxisAlignment,
crossAxisSpacing = crossAxisSpacing,
lastLineMainAxisAlignment = lastLineMainAxisAlignment,
content = content
)
}
Недостатки Compose
Спустя пару недель пришло общее понимание, как работает Compose, верстка стала более осознанной, а скорость увеличилась в разы. Но и с неприятными моментами столкнулся на личном опыте.
Баг при добавлении строкового ресурса. Происходит не всегда, но достаточно часто. Добавляешь новую строку, используешь ресурс, запускаешь приложение — а у тебя все строки поехали. Там, где должен быть текст «Номер телефона» показывается «Меню» и т.д. Лечится это только очисткой кеша и удалением приложения с эмулятора.
Обновление. Во многих статьях читал, что нужно стараться обновлять Compose до последней версии. У нас на тот момент использовалась версия 1.2.1, а последняя на тот момент была — 1.5.0. Решил обновить. Это сразу повлекло за собой обновление Gradle плагина, версии котлина и еще нескольких библиотек. И тут столкнулся с проблемой: в приложении есть поле ввода телефона, где используется маска для разбиения телефона на группы цифр. И вот эту работу с маской в Compose сломали еще в версии 1.3.0, а в версии 1.5.0 так и не починили. Причем обнаружили мы это не сразу, а спустя неделю, потому что краш воспроизводился только при тапе на поле ввода. Пришлось откатываться назад на версию 1.2.1.
Работа со списками. Для каждого элемента в списке передается уникальный id. В нашем случае, это id элемента, который пришел с сервера. Важно: этот id должен быть уникальным, иначе сразу краш.
Спустя полгода: мои рекомендации
На данный момент с Compose я работаю около полугода. За это время успел многое изучить, во многом разобраться, реализовать около 5 больших фичей. На мой взгляд, переходить на Compose стоит. Затраты времени на изучение и переход очень быстро окупятся скоростью верстки и более современным и дружелюбным UI.
Для изучения самой технологии я рекомендую:
Почитать книгу Jetpack Compose Internals. Она на английском, но читается легко, и это было полезно.
Изучать приложение Now in Android от Google.
Начать писать экраны. Можно начать с небольшого, без различных анимаций и усложнять верстку постепенно, накручивая сверху различные фишки.
Посмотреть Philipp Lackner «The Jetpack Compose Beginner Crash Course for 2023»
Комментарии (9)
Ioanna
11.10.2023 16:30+1Тоже недавно перешла на Jetpack Compose. Тяжело дается: официальная документация очень плохая, без примеров, а код из интернета обычно не работает из-за частых переименований функций. Зато много полезного реализовано из коробки.
Rusrst
11.10.2023 16:30А что именно у вас не работает и что именно было переименовано?0_о
Ioanna
11.10.2023 16:30Прямо полный список составить?)
Rusrst
11.10.2023 16:30Ну мне сложно понять что может не завестись по докам Гугла в compose. Они доку по нему в актуальном состоянии поддерживают. И уж для compose примеров то хватает, тут они озаботились лучше чем для view.
Ioanna
11.10.2023 16:30+1Больше всего сложности было с Exoplayer. Файлы не читает, к сети не подключается, а главное - не отображается, пока его не выведешь в отдельный view, но во всех примерах он работает без view.
Rusrst
11.10.2023 16:30Ну за exoplayer не скажу, сам его даже в XML не юзал
Это действительно сложности может представлять
app-z
Да. И это называется статья)) Может я чего не понимаю, как надо про композ писать?
https://habr.com/ru/articles/765416/