Как я осваивал Jetpack Compose

от автора

Привет, я Михаил Селезнев, андроид-разработчик в компании 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» 


ссылка на оригинал статьи https://habr.com/ru/articles/766814/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *