Погружение в JetPack Compose. Часть 1/2

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Минутка рекламы

Собрал здесь лучшие статьи, библиотеки и проекты на Jetpack Compose:
Jetpack Compose Awesome

Ожидания по поводу разработки пользовательского интерфейса выросли. Сегодня мы не можем создать приложение и удовлетворить потребности пользователя, не имея отточенного пользовательского интерфейса, включая анимацию и движение UI-элементов. Этих требований не существовало при создании текущего UI Toolkit-а системы Android. Чтобы решить технические проблемы быстрого и эффективного создания безупречного пользовательского интерфейса, мы представили Jetpack Compose - современный набор инструментов для создания UI, который помогает разработчикам приложений добиться успеха на этом новом поприще.

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

Какие проблемы решает Compose?

Разделение ответственности (Separation of concerns) - это хорошо известный принцип разработки программного обеспечения. Это одна из фундаментальных вещей, которую мы, как разработчики приложений, узнаем. Несмотря на то, что этот принцип хорошо известен, часто трудно понять, соблюдается ли этот принцип на практике. Может быть полезно думать об этом принципе как о термине типа «сцепление» или «связанность».

Когда мы пишем код, мы создаем модули, которые состоят из нескольких сущностей (unit-тов). Связанность (Coupling) - это зависимость между сущностями в разных модулях, которая отражает то, ка части одного модуля влияют на части других модулей. Целостность (Cohesion)- это, наоборот, взаимосвязь между сущностями (юнитами) в модуле и показывает, насколько хорошо сгруппированы юниты в модуле.

При написании поддерживаемого программного обеспечения наша цель - минимизировать связанность и максимизировать целостность.

Когда у нас есть сильно связанные модули, внесение изменений в код в одном месте означает необходимость внесения множества изменений в другие модули. Что еще хуже, связь часто может быть неявной, так что вещи ломаются в неожиданных местах из-за изменения, которое кажется совершенно не связанным.

Разделение ответственности заключается в том, чтобы сгруппировать как можно больше связанного кода, чтобы наш код можно было легко поддерживать и масштабировать по мере роста приложения.

Давайте перейдем к практике, и рассмотрим современные подходы решения этого вопроса в мире Android-разработки. Возьмем для примера ViewModel и XML-лейаут.

ViewModel предоставляет данные лейауту. Оказывается, здесь может быть спрятано много зависимостей: большая взаимосвязь между ViewModel и лейаутом. Один из наиболее частых и х хорошо знакомых нам случаев сильной взаимосвязи - это использование API (от Android или сторонних библиотек - прим. переводчика), в которых требуется знание о внутренностях самого XML-макета, например метод findViewByID.

Использование таких API требует знания того, как устроен XML-макет, и создает взаимосвязь между ними. Поскольку наше приложение со временем растет, мы должны следить за тем, чтобы ни одна из этих зависимостей не устарела.

Большинство современных приложений отображают пользовательский интерфейс динамически и меняются в процессе выполнения. В результате необходимо не только проверить, что эти зависимости (т. е. View-элементы) предоставляются XML-макетом, но также и то, что они будут предоставляться во время работы программы. Если элемент покидает иерархию View во время выполнения, некоторые из этих зависимостей могут быть нарушены и могут привести к таким проблемам, как NulReferenceExceptions.

Примечание переводчика:

Под "предоставлением зависимостей" имеется в виду наличие вьюшки в самом лейауте и возможность найти её через findViewById.

Обычно ViewModel определяется языке программирования Kotlin, а макет - в XML. Из-за этой разницы в языке существует принудительное разделение, хотя ViewModel и XML-макет иногда могут быть тесно связаны. Другими словами, они очень тесно связаны.

Возникает вопрос: что, если бы мы начали определять лейаут, т. е. структуру нашего пользовательского интерфейса на одном языке? Что, если мы выберем Kotlin?

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

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

Но то, что может сделать фреймворк, - это предоставить вам инструменты, упрощающие разделение: этим инструментом является функция Composable. Функции - это то, что вы, вероятно, использовали в течение долгого времени для разделения задач в других местах вашего кода. Навыки, которые вы приобрели для такого рода рефакторинга и написания надежного, поддерживаемого, чистого кода, - те же навыки применимы и к Composable -функциям.

Устройство Composable-функции

Это пример Composable-функции.

@Composable
fun App(appData: AppData) {
  val derivedData = compute(appData)
  Header()
  if (appData.isOwner) {
    EditButton()
  }
  Body {
    for (item in derivedData.items) {
      Item(item)
    }
  }
}

Здесь функция получает данные как параметры из класса appData. В идеале это неизменяемые данные, которые Composable-функция не меняет: Composable-функция должна быть функцией преобразования для этих данных. Следовательно, мы можем использовать любой код на Kotlin, чтобы взять эти данные и использовать их для описания нашей иерархии, например вызвав функции Header() и Body().

Это означает, что мы вызываем другие Composable-функции, и эти вызовы отражают структуру нашего UI. Мы можем использовать все примитивы предоставляемые Kotlin-ом. Мы можем включить операторы if и циклы for для управления структурой UI, чтобы справиться с более сложной логикой пользовательского интерфейса.

Composable-функции часто используют конечный лямбда-синтаксис Kotlin, поэтому Body() - это Сomposable-функция, которая принимает composable-лямбду в качестве параметра. Это подразумевает иерархию или структуру, поэтому Body() обертывает здесь набор элементов.

Декларативный UI

Декларативный - это модное, но важное слово. Когда мы говорим о декларативном программировании, мы говорим об его отличии от императивного программирования. Давайте рассмотрим пример.

Рассмотрим почтовое приложение со значком непрочитанных сообщений. Если сообщений нет, приложение отображает пустой конверт. Если есть какие-то сообщения, мы визуализируем бумагу в конверте, а если есть 100 сообщений, мы визуализируем значок, как будто он горит.

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

fun updateCount(count: Int) {
  if (count > 0 && !hasBadge()) {
    addBadge()
  } else if (count == 0 && hasBadge()) {
    removeBadge()
  }
  if (count > 99 && !hasFire()) {
    addFire()
    setBadgeText("99+")
  } else if (count <= 99 && hasFire()) {
    removeFire()
  }
  if (count > 0 && !hasPaper()) {
   addPaper()
  } else if (count == 0 && hasPaper()) {
   removePaper()
  }
  if (count <= 99) {
    setBadgeText("$count")
  }
}

В этом коде мы получаем новое количество сообщений и должны выяснить, как обновить текущий пользовательский интерфейс, чтобы отразить это состояние. Здесь много корнер-кейсов, и эта логика непроста, хотя это относительно простой пример.

Если мы перепишем эту логику в декларативном стиле, мы получим нечто подобное:

@Composable
fun BadgedEnvelope(count: Int) {
  Envelope(fire=count > 99, paper=count > 0) {
    if (count > 0) {
      Badge(text="$count")
    }
  }
}

Здесь мы говорим:

  • Если счет больше 99, покажи огонь

Источник: https://habr.com/ru/post/533510/


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

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

В последнее время на Хабре стали чаще появляться посты о том, как хорош Telegram, как гениальны и опытны братья Дуровы в построении сетевых систем, и т.п. В то же время, очень мало кто действител...
Холивар. История рунета. Часть 1. Начало: хиппи из Калифорнии, Носик и лихие 90-е В конце 90-х монополия интеллектуалов на рунет была нарушена. В царство завлабов и филологов ворвались два юны...
Трудно поверить, что этот ветхозаветный старец из зажиточного пригорода Cан-франциско один из отцов-основателей рунета. Joel Schatz — учёный, визионер, идеалист и бизнесмен, в молодости л...
Перехватить конфиденциальную информацию? Получить несанкционированный доступ к различным приложениям и системам? Нарушить нормальный режим работы? Все это и многое другое выполняют атаки типа...
СДСМ закончился, а бесконтрольное желание писать — осталось. Долгие годы наш брат страдал от выполнения рутинной работы, скрещивал пальцы перед коммитом и недосыпал из-за ночных ролбэков....