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


  1. app-z
    11.10.2023 16:30
    +2

    Да. И это называется статья)) Может я чего не понимаю, как надо про композ писать?

    https://habr.com/ru/articles/765416/


  1. app-z
    11.10.2023 16:30
    -2

    Я плюсанул статью если что, надеюсь автор напишет цикл статей. Будем обсуждать


    1. app-z
      11.10.2023 16:30

      Я когото обидел)


  1. Ioanna
    11.10.2023 16:30
    +1

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


    1. Rusrst
      11.10.2023 16:30

      А что именно у вас не работает и что именно было переименовано?0_о


      1. Ioanna
        11.10.2023 16:30

        Прямо полный список составить?)


        1. Rusrst
          11.10.2023 16:30

          Ну мне сложно понять что может не завестись по докам Гугла в compose. Они доку по нему в актуальном состоянии поддерживают. И уж для compose примеров то хватает, тут они озаботились лучше чем для view.


          1. Ioanna
            11.10.2023 16:30
            +1

            Больше всего сложности было с Exoplayer. Файлы не читает, к сети не подключается, а главное - не отображается, пока его не выведешь в отдельный view, но во всех примерах он работает без view.


            1. Rusrst
              11.10.2023 16:30

              Ну за exoplayer не скажу, сам его даже в XML не юзал

              Это действительно сложности может представлять