This media is not supported in your browser
VIEW IN TELEGRAM
Compose withAnimation
В SwiftUI есть очень классная фича
Не справедливо, что такого механизма нет из коробки в Compose, и инженер из Google решил исправить это недоразумение. Он сделал свой аналог
Как это работает?
1. Создается пустой словарь состояний для анимации
2. Выполняется лямбда блок внутри Snapshot, в этой лямбде могут происходить изменения стейта
3. У Snapshot вызывается
4. Данные мапятся в другой тип, откуда достаются измененные значения
5. Уничтожается Snapshot, чтобы не допустить утечек памяти, при этом изменения не применяются глобально! «Все что произошло в снапшоте, остается в снапшоте»©
6. Анимируются значения
Если у вас есть еще идеи как можно применить снапшоты, делитесь своими мыслями в комментариях👇
#Compose #Snapshots #Animations
В SwiftUI есть очень классная фича
withAnimation
, позволяющая сделать анимацию вьюшки просто путем изменения состояния, а сама анимация произойдет как по волшебству.
@State private var showDetail = false
var body: some View {
VStack {
Button("Show details") {
withAnimation {
showDetail.toggle()
}
}
if showDetail {
Text("Details")
}
}
}
Не справедливо, что такого механизма нет из коробки в Compose, и инженер из Google решил исправить это недоразумение. Он сделал свой аналог
withAnimation
и реализовал это с помощью Snapshot API, про который мы говорили ранее.Как это работает?
1. Создается пустой словарь состояний для анимации
2. Выполняется лямбда блок внутри Snapshot, в этой лямбде могут происходить изменения стейта
3. У Snapshot вызывается
writeObserver
при каждой записи в State и заполняется информация для анимации4. Данные мапятся в другой тип, откуда достаются измененные значения
5. Уничтожается Snapshot, чтобы не допустить утечек памяти, при этом изменения не применяются глобально! «Все что произошло в снапшоте, остается в снапшоте»©
6. Анимируются значения
internal suspend fun withAnimation(
adapterRegistry: StateObjectAdapterRegistry,
animationSpec: AnimationSpec<Any?>,
block: () -> Unit
) {
val statesToAnimate = mutableMapOf<Any, StateObjectAdapter>() // 1
val snapshot = Snapshot.takeMutableSnapshot(
writeObserver = { changedState ->
statesToAnimate[changedState] = checkNotNull(adapterRegistry.getAdapterFor(changedState)) // 3
}
)
val targetValues = snapshot.enter {
block() // 2
buildTargetValues(statesToAnimate) // 4
}
snapshot.dispose() // 5
animateValues(targetValues, animationSpec) // 6
}
Если у вас есть еще идеи как можно применить снапшоты, делитесь своими мыслями в комментариях👇
#Compose #Snapshots #Animations
🔥10👍2
Forwarded from Compose Broadcast (Alex Panov)
This media is not supported in your browser
VIEW IN TELEGRAM
Автор крутого доклада про компиляторные плагины для Compose с предыдущего Mobius опубликовал исходники плагинов на GitHub.
Там очень много всего интересного и полезного:
👉 Анализ стабильности параметров Composable функции
👉 Подсветка рекомпозиций в UI
👉 Автоматическая генерация и удаление testTag
👉 Логирование причин рекомпозиции и другое
Эти плагины наконец-то решают извечную проблему анализа лишних рекомпозиций и оптимизаций вашего кода в Compose, теперь делать высокопроизводительные приложения стало гораздо проще!
#compose #plugins
Там очень много всего интересного и полезного:
👉 Анализ стабильности параметров Composable функции
👉 Подсветка рекомпозиций в UI
👉 Автоматическая генерация и удаление testTag
👉 Логирование причин рекомпозиции и другое
Эти плагины наконец-то решают извечную проблему анализа лишних рекомпозиций и оптимизаций вашего кода в Compose, теперь делать высокопроизводительные приложения стало гораздо проще!
#compose #plugins
👍12👏3
Кастомные маски для TextField в Compose
Раньше в Android View реализовать маску для номера телефона, не говоря уже про что-то кастомное было далеко не самой простой задачей и люди, чтобы облегчить себе жизнь, использовали сторонние библиотеки, такие как Decoro.
Но теперь с приходом Compose надобность в сторонних решениях практически отпала, ведь реализовать кастомную маску для TextField можно буквально в 40 строк кода😱 , и это возможно благодаря продуманному и простому API интерфейса
Фишка в том, что
🔸 Определить как исходный текст будет трансформироваться в текст с маской
🔸 Предоставить двухсторонний маппинг для правильного смещения курсора в поле ввода
📘 Подробнее можно почтитать в статье, где разобран случай маски для ввода даты и с полностью кастомизируемой маской
#Compose
Раньше в Android View реализовать маску для номера телефона, не говоря уже про что-то кастомное было далеко не самой простой задачей и люди, чтобы облегчить себе жизнь, использовали сторонние библиотеки, такие как Decoro.
Но теперь с приходом Compose надобность в сторонних решениях практически отпала, ведь реализовать кастомную маску для TextField можно буквально в 40 строк кода
VisualTransformation
.Фишка в том, что
VisualTransformation
, как бы это неожиданно не звучало, влияет всего лишь на визуальное отображение, а не реальное значение поля, и, чтобы реализовать любую маску, достаточно сделать две вещи:
var out = ""
text.text.forEachIndexed { index, char ->
when (index) {
2 -> out += "/$char"
4 -> out += "/$char"
else -> out += char
}
}
val numberOffsetTranslator = object : OffsetMapping {
override fun originalToTransformed(offset: Int): Int {
if (offset <= 2) return offset
if (offset <= 4) return offset + 1
return offset + 2
}
override fun transformedToOriginal(offset: Int): Int {
if (offset <= 2) return offset
if (offset <= 5) return offset - 1
return offset - 2
}
}
#Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍4❤1😁1
This media is not supported in your browser
VIEW IN TELEGRAM
Коллега из Контура, Евгений Мельцайкин, написал статью о том, как сделать такую кнопку с помощью кастомного Layout в Compose и как оптимизировать ее, чтобы достичь минимального количества рекомпозиций. Приятного чтения 📕
Исходный код можно посмотреть здесь🐱
А вы сможете на взгляд определить какая кнопка сделана оптимально по количеству рекомпозиций?
#Compose
Исходный код можно посмотреть здесь
А вы сможете на взгляд определить какая кнопка сделана оптимально по количеству рекомпозиций?
#Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥37👍2
Скриншот-тестирование в Compose
Google не так давно выкатили свой тулинг для скриншот-тестирования в Compose в экспериментальном режиме и работает он на основе Compose Preview👀
У меня довольно скпептическое отношение к превью, за все время работы с Compose у меня постоянно были какие-то проблемы с этим механизмом, а с приходом Compose Multiplatform заставить превьюшки работать тот ещё челлендж, более менее дела с превью обстоят только в новой IDE Fleet, но там ещё ворох других проблем.
Так вот, вернёмся к тестированию, я попробовал этот способ и что могу сказать по текущему состоянию тулинга:
👍 Официальное решение для скриншот-тестирования
👍 Генерация отчёта с диффом изображений
👍 Уже относительно работает и можно использовать на свой страх и риск
👍 Тесты прогоняются без эмулятора и соответственно прогоняются быстро
👎 Названия сгенерированных скриншотов нельзя поменять
👎 Нельзя выборочно обновить эталонный скриншот
👎 Нужно использовать специальные gradle таски для валидации скриншотов, потребуются доработки на CI
👎 Нельзя настроить минимальный порог отличий между скриншотами
👎 Только для Android
Так что подводя итоги, круто, что появляется решение из коробки, но в текущем состоянии завязываться на него довольно опасно.
#Compose #SnapshotTesting
Google не так давно выкатили свой тулинг для скриншот-тестирования в Compose в экспериментальном режиме и работает он на основе Compose Preview
У меня довольно скпептическое отношение к превью, за все время работы с Compose у меня постоянно были какие-то проблемы с этим механизмом, а с приходом Compose Multiplatform заставить превьюшки работать тот ещё челлендж, более менее дела с превью обстоят только в новой IDE Fleet, но там ещё ворох других проблем.
Так вот, вернёмся к тестированию, я попробовал этот способ и что могу сказать по текущему состоянию тулинга:
Так что подводя итоги, круто, что появляется решение из коробки, но в текущем состоянии завязываться на него довольно опасно.
#Compose #SnapshotTesting
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍8
Нашли серьезную уязвимость в Jetpack Navigation Compose, которая позволяет открыть любой экран в приложении, даже если там нет явных диплинков ⚠️
Эксплуатируется она максимально просто, достаточно знать имя пакета и название маршрута в графе навигации:
Как защититься
1. Разумеется лучший вариант не использовать данную навигацию, можете посмотреть мой пост со сравнением библиотек навигации для Compose и выбрать подходящую
2. Если в приложении не используются диплинки, можно частично решить проблему перетерев data в определенном intent:
#Security #Compose
@kotlin_adept
Эксплуатируется она максимально просто, достаточно знать имя пакета и название маршрута в графе навигации:
Intent().apply {
setClassName("your.package", "your.package.MainActivity")
data = Uri.parse("android-app://androidx.navigation/YOUR_DESTINATION")
startActivity(this)
}
Как защититься
1. Разумеется лучший вариант не использовать данную навигацию, можете посмотреть мой пост со сравнением библиотек навигации для Compose и выбрать подходящую
2. Если в приложении не используются диплинки, можно частично решить проблему перетерев data в определенном intent:
val intentData = intent.dataString
if (intentData != null && intentData.startsWith("android-app://androidx.navigation")) {
intent.setData(null)
}
#Security #Compose
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
😁13👍6🔥5😱4👀2👻1
Перевернутые модификаторы
Неудивительно, что Android и iOS разработчики часто не могут найти общий язык, ведь у них (у нас) все перевернуто с ног на голову 🇦🇺
Это касается и модификаторов в декларативных UI фреймворках. На картинке видно, что цепочка из одинаковых модификаторов для Compose и SwiftUI дают один и тот же результат, при этом располагаясь в обратном порядке.
➡️ В Compose первый модификатор size задает минимальные и максимальные констрейнты и мы не можем выйти за эти ограничения, не переопределяя их.
➡️ В SwiftUI таких ограничений нет и там всегда padding применяется во вне, что может быть даже удобнее, так как не приходится об этом задумываться.
🗓 Но к чему я это все? На ближайшей конференции Мобиус буду рассказывать доклад, где сравню ключевые отличия обоих фреймворков, и если тема интересна, то буду рад видеть всех на докладе 😉
#Compose #SwiftUI
@kotlin_adept
Неудивительно, что Android и iOS разработчики часто не могут найти общий язык, ведь у них (у нас) все перевернуто с ног на голову 🇦🇺
Это касается и модификаторов в декларативных UI фреймворках. На картинке видно, что цепочка из одинаковых модификаторов для Compose и SwiftUI дают один и тот же результат, при этом располагаясь в обратном порядке.
#Compose #SwiftUI
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
👍23🔥9❤1😁1🤪1
This media is not supported in your browser
VIEW IN TELEGRAM
Пока готовился к докладу, нашел неплохой репозиторий с набором разных анимаций для Compose Multiplatform.
Там вы найдете множество разных примеров:
🟣 Анимации заставок разных приложений (Netflix, Twitter, GitHub, Slack и др.)
🟣 Кастомный pull-to-refresh
🟣 Анимация горения свечи
🟣 Упоротая сова из Duolingo
А если вы iOS разработчик, то вот вам еще более классный репозиторий с кучей красивых анимаций для SwiftUI💅
#Animation #Compose #KMP #SwiftUI
@kotlin_adept
Там вы найдете множество разных примеров:
А если вы iOS разработчик, то вот вам еще более классный репозиторий с кучей красивых анимаций для SwiftUI
#Animation #Compose #KMP #SwiftUI
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
👍25😍1
This media is not supported in your browser
VIEW IN TELEGRAM
Адаптивный UI проще, чем кажется
Раньше с Android View, если требовалось поддержать верстку для планшетов, довольно часто просто делали отдельную верстку с нуля, и несмотря на то, что можно было расположить несколько фрагментов на одном экране, это не избавляло от сложностей навигации🥲
Теперь же с приходом Compose и нового api делать адаптивную верстку стало значительно проще. И вот несколько рекомендаций как сделать современный адаптивный UI:
1️⃣ Не используйте флаги вроде isTablet и т.д., используйте window size classes для динамического определения размера окна: Compact, Medium, Expanded
2️⃣ Используйте готовые адаптивные компоненты вроде ListDetailPaneScaffold, SupportingPaneScaffold, NavigationSuiteScaffold
3️⃣ Рассмотрите возможность использования LazyGrid вместо LazyList
4️⃣ Меняйте расположение UI компонентов с помощью BoxWithConstraint и movableContentOf во избежание лишних рекомпозиций
5️⃣ Не блокируйте ориентацию экрана и не отключайте resizeableActivity
6️⃣ Меняйте размер и соотношение сторон у UI компонентов в зависимости от размеров окна
🌳 В Decompose также появилась поддержка адаптивной навигации и благодаря ChildPanels реализовать list-detail навигацию стало очень просто без лишнего бойлерплейта.
А есть ли адаптивная верстка в вашем приложении❓
🫡 — только screenOrientation portrait, только хардкор
😎 — есть адаптивная верстка под любые экраны
#Compose #AdaptiveUI
@kotlin_adept
Раньше с Android View, если требовалось поддержать верстку для планшетов, довольно часто просто делали отдельную верстку с нуля, и несмотря на то, что можно было расположить несколько фрагментов на одном экране, это не избавляло от сложностей навигации
Теперь же с приходом Compose и нового api делать адаптивную верстку стало значительно проще. И вот несколько рекомендаций как сделать современный адаптивный UI:
А есть ли адаптивная верстка в вашем приложении
🫡 — только screenOrientation portrait, только хардкор
😎 — есть адаптивная верстка под любые экраны
#Compose #AdaptiveUI
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
🫡55😎6👍5❤🔥2❤1
Compose Multiplatform в проде
Хочу поделиться новостью: мы выпустили первое приложение, полностью написанное на Compose Multiplatform для iOS😌
Изначально приложение разрабатывалось только для Android, но использовался Kotlin-стек (Decompose, Ktor, SqlDelight, Koin) и обычный Jetpack Compose. Чтобы запустить его в каком-то виде на iOS, потребовалось всего 4 дня! Конечно, доведение до релиза заняло значительно больше времени, но всё равно это оказалось гораздо быстрее, чем полноценная разработка аналогичного проекта с нуля.
Что по итогам:
🟣 Compose в релизной версии вполне прилично работает, особенно на новых устройствах с поддержкой 120 Гц
🟣 Управление жестами удалось легко реализовать благодаря Decompose
🟣 Скролл подлагивает и не ощущается как нативный
🟣 BottomSheet, как всегда причиняет боль 😬
🟣 Есть некоторые баги с TextField
🟣 Некоторые контролы пришлось реализовать нативно, например, WebView, TimePicker и т.д.
Тем не менее, я уверен, что многие проблемы будут исправлены в будущем и уже сейчас Compose Multiplatform можно использовать в проектах, где плавность интерфейса не является критически важной👍
#iOS #Compose
Хочу поделиться новостью: мы выпустили первое приложение, полностью написанное на Compose Multiplatform для iOS
Изначально приложение разрабатывалось только для Android, но использовался Kotlin-стек (Decompose, Ktor, SqlDelight, Koin) и обычный Jetpack Compose. Чтобы запустить его в каком-то виде на iOS, потребовалось всего 4 дня! Конечно, доведение до релиза заняло значительно больше времени, но всё равно это оказалось гораздо быстрее, чем полноценная разработка аналогичного проекта с нуля.
Что по итогам:
Тем не менее, я уверен, что многие проблемы будут исправлены в будущем и уже сейчас Compose Multiplatform можно использовать в проектах, где плавность интерфейса не является критически важной
#iOS #Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥55👍5👏2🤔2❤1
Как подружить Web History и Compose resources
Недавно столкнулся с проблемой: после добавления поддержки Web History в проект с Compose для Web у меня перестали работать ресурсы, причём это происходило только на вложенных экранах.
Изначально я предположил, что проблема связана с настройками веб-сервера, но нет. В Compose для Web ресурсы загружаются по относительному пути. Это означает, что к текущему URL в браузере добавляется путь до ресурсов. Соответственно, если вы находитесь не на главной странице, то по такому пути ресурсы окажутся недоступными🫥
Посмотрел, что пишут в документации, но никаких рекомендаций там не дается на этот счет, что довольно странно. Поправить же проблему удалось следующим образом:
Добавляем этот код в функцию main в сорсете jsMain, и пути до ресурсов снова становятся корректными.
#Compose #JS #WEB
Недавно столкнулся с проблемой: после добавления поддержки Web History в проект с Compose для Web у меня перестали работать ресурсы, причём это происходило только на вложенных экранах.
Изначально я предположил, что проблема связана с настройками веб-сервера, но нет. В Compose для Web ресурсы загружаются по относительному пути. Это означает, что к текущему URL в браузере добавляется путь до ресурсов. Соответственно, если вы находитесь не на главной странице, то по такому пути ресурсы окажутся недоступными
Посмотрел, что пишут в документации, но никаких рекомендаций там не дается на этот счет, что довольно странно. Поправить же проблему удалось следующим образом:
configureWebResources {
resourcePathMapping { path -> "${location.origin}/$path" }
}
Добавляем этот код в функцию main в сорсете jsMain, и пути до ресурсов снова становятся корректными.
#Compose #JS #WEB
Please open Telegram to view this post
VIEW IN TELEGRAM
👍22😨4
Я снова принял участие в программном комитете конференции Podlodka Android Crew.
И нам нужна ваша помощь в выборе темы нового сезона. Какая из предложенных тем вам ближе, и вы бы хотели посетить сезон, посвящённый ей?
#️⃣ Compose: 3 года в продакшене
#️⃣ Тесты 360
#️⃣ Карьера Android-разработчика
Заполните, пожалуйста, форму. Среди участников, заполнивших форму, мы разыграем проходку на конференцию.
И нам нужна ваша помощь в выборе темы нового сезона. Какая из предложенных тем вам ближе, и вы бы хотели посетить сезон, посвящённый ей?
Заполните, пожалуйста, форму. Среди участников, заполнивших форму, мы разыграем проходку на конференцию.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18
Пока мы далеко не отошли от темы Bottom Sheet, хочу снова немного побомбить на то, какой API нам предоставили разработчики этого компонента в Material 3.
Я уже как-то поднимал тему декларативного Bottom Sheet, когда решение о том, показывать его или нет, определяется исключительно состоянием. То есть мы показываем шторку, если ассоциированный с ней стейт ≠ null, иначе скрываем.
И казалось бы, в Material 3 сделали именно так: достаточно просто установить значение false в переменной showBottomSheet, чтобы скрыть его. Но тогда это произойдет без анимации сворачивания компонента⚠️
Чтобы это исправить, придется явно вызывать suspend-функцию hide, но делать это каждый раз, мягко говоря, неудобно. Можно попробовать написать свою декларативную обертку, но придется решить несколько проблем:
🔘 Как сохранять контент при анимации скрытия, если стейта уже нет?
🔘 Как запретить перехватывать Bottom Sheet жестом, пока он сворачивается?
И вторая проблема самая неприятная, так как в Bottom Sheet нельзя отключить обработку жестов, пока он скрывается. Но нам обязательно нужно скрыть его, если ассоциированный стейт уже null, иначе получим неконсистентное состояние.
Как ни странно, в SwiftUI таких проблем нет — декларативная обертка пишется буквально в несколько строчек, что можно увидеть на изображении.
Обертку для Bottom Sheet из Material 3, которая отлично подходит для Slot навигации в Decompose, я уже реализовал и чуть позже поделюсь ею с вами, когда обновлю свой пример KMP-проекта.
#Compose #SwiftUI #BottomSheet
Я уже как-то поднимал тему декларативного Bottom Sheet, когда решение о том, показывать его или нет, определяется исключительно состоянием. То есть мы показываем шторку, если ассоциированный с ней стейт ≠ null, иначе скрываем.
И казалось бы, в Material 3 сделали именно так: достаточно просто установить значение false в переменной showBottomSheet, чтобы скрыть его. Но тогда это произойдет без анимации сворачивания компонента
Чтобы это исправить, придется явно вызывать suspend-функцию hide, но делать это каждый раз, мягко говоря, неудобно. Можно попробовать написать свою декларативную обертку, но придется решить несколько проблем:
И вторая проблема самая неприятная, так как в Bottom Sheet нельзя отключить обработку жестов, пока он скрывается. Но нам обязательно нужно скрыть его, если ассоциированный стейт уже null, иначе получим неконсистентное состояние.
Как ни странно, в SwiftUI таких проблем нет — декларативная обертка пишется буквально в несколько строчек, что можно увидеть на изображении.
Обертку для Bottom Sheet из Material 3, которая отлично подходит для Slot навигации в Decompose, я уже реализовал и чуть позже поделюсь ею с вами, когда обновлю свой пример KMP-проекта.
#Compose #SwiftUI #BottomSheet
Please open Telegram to view this post
VIEW IN TELEGRAM
👍27😢7💯5
Иногда при работе с TextField нам нужно управлять положением курсора — например, когда мы хотим отредактировать какой-то текст и вставить его в TextField. В таком случае курсор может остаться в начале строки. Для этого у TextField есть перегрузка, которая принимает TextFieldValue. Однако мы не можем использовать Compose-сущности в модулях без Compose.
Теперь посмотрите на код на изображении. Как думаете, в чём здесь проблема?
Колбэк onValueChange зациклится.
Но, несмотря на это, на большинстве устройств всё будет работать нормально: текст будет вводиться, и курсором можно будет управлять. Однако это не касается устройств Huawei — там текст будет вводиться через раз. Сначала я хотел поругать китайцев, но теперь хочу их похвалить: если бы не устройства Huawei, мы бы не отловили эту проблему.
Давайте разберёмся, почему так происходит:
1. При редактировании текста изменяются messageText и lastSelection.
2. Эти состояния объединяются, и изменяется messageTextField.
3. Устанавливается новое значение в TextField.
4. Снова вызывается onValueChange, поскольку TextFieldValue отличается. А отличается он из-за параметра composition, который неявно меняет сам Compose. Мы его не учитываем — из-за этого и происходит бесконечный цикл.
Как это исправить?
Самый логичный вариант — не использовать локальные состояния, а хранить TextFieldValue напрямую в стейте ViewModel. Но если такой возможности нет, можно создать аналогичный класс в бизнес-логике и маппить значения, главное учитывать все параметры TextFieldValue:
#Compose
Теперь посмотрите на код на изображении. Как думаете, в чём здесь проблема?
Но, несмотря на это, на большинстве устройств всё будет работать нормально: текст будет вводиться, и курсором можно будет управлять. Однако это не касается устройств Huawei — там текст будет вводиться через раз. Сначала я хотел поругать китайцев, но теперь хочу их похвалить: если бы не устройства Huawei, мы бы не отловили эту проблему.
Давайте разберёмся, почему так происходит:
1. При редактировании текста изменяются messageText и lastSelection.
2. Эти состояния объединяются, и изменяется messageTextField.
3. Устанавливается новое значение в TextField.
4. Снова вызывается onValueChange, поскольку TextFieldValue отличается. А отличается он из-за параметра composition, который неявно меняет сам Compose. Мы его не учитываем — из-за этого и происходит бесконечный цикл.
Как это исправить?
Самый логичный вариант — не использовать локальные состояния, а хранить TextFieldValue напрямую в стейте ViewModel. Но если такой возможности нет, можно создать аналогичный класс в бизнес-логике и маппить значения, главное учитывать все параметры TextFieldValue:
TextField(
value = messageState.toComposeTextFieldValue(),
onValueChange = { viewModel.onTextChanged(it.toDomainTextFieldValue()) }
)
#Compose
🔥29
Обычно SwiftUI и Compose очень похожи между собой, и, как правило, стейт для экранов можно формировать одинаково — это очень удобно при работе с KMP.
Но иногда бывают сильные отличия в API, например, PullToRefresh: если в Compose индикатор показывается по изменению состояния, то в SwiftUI — это асинхронная таска🤬
Недавно мы наткнулись на ещё один такой компонент — контекстное меню. На слайде видно, насколько более громоздко выглядит код на Compose, но не все так однозначно. Здесь разница в том, что, опять же, если в Compose меню показывается в зависимости от состояния, то в SwiftUI мы сразу должны знать, какие элементы отображать в этом меню, и нельзя сделать это по клику. Это неудобно, если элементы контекстного меню формируются динамически.
Чтобы исправить это, придётся в стейте сразу хранить словарь и в зависимости от типа ячейки, на которую кликнули, выбирать нужный набор значений.
💬 А какие ошибки допускали вы при формировании стейта экрана?
#Compose #SwiftUI
Но иногда бывают сильные отличия в API, например, PullToRefresh: если в Compose индикатор показывается по изменению состояния, то в SwiftUI — это асинхронная таска
Недавно мы наткнулись на ещё один такой компонент — контекстное меню. На слайде видно, насколько более громоздко выглядит код на Compose, но не все так однозначно. Здесь разница в том, что, опять же, если в Compose меню показывается в зависимости от состояния, то в SwiftUI мы сразу должны знать, какие элементы отображать в этом меню, и нельзя сделать это по клику. Это неудобно, если элементы контекстного меню формируются динамически.
Чтобы исправить это, придётся в стейте сразу хранить словарь и в зависимости от типа ячейки, на которую кликнули, выбирать нужный набор значений.
#Compose #SwiftUI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20🔥3
UI-тестирование в Compose 🎨
Если вы хотите реализовать UI-тесты в Compose, то на сегодняшний день есть три основных решения: официальная библиотека от Google и обёртки над ней — Kakao/Kaspresso и Ultron. Про официальное решение сегодня говорить не будем, а рассмотрим поближе другие библиотеки.
Мы уже много лет используем Kaspresso для UI-тестов, но недавно я решил поближе познакомиться с библиотекой Ultron и сравнить оба решения. На самом деле, по функциональности они очень схожи.
🟡 Ultron выглядит довольно заманчиво: у него есть два ключевых преимущества перед конкурентами — при написании тестов на Ultron практически отсутствует какой-либо бойлерплейт-код, а также Ultron поддерживает Compose Multiplatform. Ещё из плюсов можно выделить качественную документацию. Из недостатков я бы отметил довольно небольшое комьюнити у библиотеки, а также, лично для меня, оказалось непривычным отсутствие связей parent-child между PageObject. Подробнее узнать про Ultron можно в недавно опубликованном докладе от автора библиотеки — Алексея Тюрина.
🔘 Kakao/Kaspresso — проверенные временем библиотеки с большим комьюнити, но описание PageObject в Kakao до версии 1.0 требовало довольно много бойлерплейт-кода, особенно при работе с Lazy-списками. Также механизм flaky safety в Kaspresso не совсем хорошо работает с виртуальным временем в Compose-тестах. Об этом подробно рассказал Паша Стрельченко в своём докладе.
💬 А что используете вы для UI-тестирования в Compose? И оправдан ли тренд на уход от дорогих UI-тестов в проекте?
#Compose #UiTesting
Если вы хотите реализовать UI-тесты в Compose, то на сегодняшний день есть три основных решения: официальная библиотека от Google и обёртки над ней — Kakao/Kaspresso и Ultron. Про официальное решение сегодня говорить не будем, а рассмотрим поближе другие библиотеки.
Мы уже много лет используем Kaspresso для UI-тестов, но недавно я решил поближе познакомиться с библиотекой Ultron и сравнить оба решения. На самом деле, по функциональности они очень схожи.
💬 А что используете вы для UI-тестирования в Compose? И оправдан ли тренд на уход от дорогих UI-тестов в проекте?
#Compose #UiTesting
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17
Как встроить SwiftUI в Compose Multiplatform
Обычно я стараюсь избегать использования кастомных CompositionLocal в Compose, так как это добавляет неявные зависимости, и если не предоставить значение, приложение упадёт в рантайме. Я придерживаюсь подхода, в котором CompositionLocal можно использовать только тогда, когда значение действительно может быть полезно любой Composable-функции в дереве. Яркий пример — тема приложения.
И при работе с Compose Multiplatform я подсмотрел классное применение этого механизма для встраивания SwiftUI вьюшек в Composable функции.
1. В сорсете iosMain создаём CompositionLocal и интерфейс NativeViewFactory.
2. На стороне Swift реализуем этот интерфейс и передаём его в функцию создания UIViewController.
3. В этой функции пробрасываем фабрику через CompositionLocalProvider.
4. Далее в любом месте поддерева в iosMain можно получить доступ к этой нативной вьюшке.
🌐 Посмотреть пример приложения для сканирования QR-кодов с этим подходом можно в репозитории, который я подготовил для лекции в онлайн-университете.
#Compose #SwiftUI
Обычно я стараюсь избегать использования кастомных CompositionLocal в Compose, так как это добавляет неявные зависимости, и если не предоставить значение, приложение упадёт в рантайме. Я придерживаюсь подхода, в котором CompositionLocal можно использовать только тогда, когда значение действительно может быть полезно любой Composable-функции в дереве. Яркий пример — тема приложения.
И при работе с Compose Multiplatform я подсмотрел классное применение этого механизма для встраивания SwiftUI вьюшек в Composable функции.
1. В сорсете iosMain создаём CompositionLocal и интерфейс NativeViewFactory.
2. На стороне Swift реализуем этот интерфейс и передаём его в функцию создания UIViewController.
3. В этой функции пробрасываем фабрику через CompositionLocalProvider.
4. Далее в любом месте поддерева в iosMain можно получить доступ к этой нативной вьюшке.
#Compose #SwiftUI
Please open Telegram to view this post
VIEW IN TELEGRAM
❤🔥10🔥5👍2❤1
Make Snackbar Great Again
В Compose реализация показа snackbar получилась не самой удобной, на каждом экране приходится:
🔘 Оборачивать контент в Scaffold
🔘 Вызывать suspend-функции для показа snackbar
🔘 Не забывать предусмотреть отмену предыдущего snackbar
🔘 Страдать из-за BottomSheet, так как там не получится использовать Scaffold, иначе он растянется на весь экран
И даже если сделать один глобальный обработчик, snackbar будет залезать на BottomNavigation, что выглядит довольно плохо🤔
Вот что я предлагаю:
🟢 Отказаться от показа snackbar через Scaffold и написать простую логику с декларативным отображением snackbar с анимацией в зависимости от состояния.
🟢 Чтобы snackbar не перекрывал лишние элементы интерфейса, можно написать кастомный модификатор в Compose, который будет запоминать верхнюю позицию элемента, к которому он применён, и добавлять неявный отступ к snackbar.
Таким образом, эта реализация не только решает проблемы выше, но и позволяет отображать snackbar как внизу, так и вверху экрана.
🔥 Если тема интересна, ставьте реакции и я попробую собрать пример с такой реализацией.
#Compose
В Compose реализация показа snackbar получилась не самой удобной, на каждом экране приходится:
И даже если сделать один глобальный обработчик, snackbar будет залезать на BottomNavigation, что выглядит довольно плохо
Вот что я предлагаю:
Таким образом, эта реализация не только решает проблемы выше, но и позволяет отображать snackbar как внизу, так и вверху экрана.
#Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥121🥴9👍8 4
Преимущества библиотеки:
#Compose #Snackbar #KMP
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥68👍9❤4🤔1
Как слить кодовую базу из-за Compose Multiplatform
Я уже как-то писал, что никакая кроссплатформа не освобождает от необходимости знать среду выполнения, и это в очередной раз стрельнуло.
Если в Android мы переживаем за то, чтобы никто не зареверсил наш код, подключаем R8 и так далее, то как обеспечить такой же уровень защиты в вебе?
Казалось бы, там всё ок из коробки: в релизном билде что-то понять из итогового JS-файла не получится, особенно с Wasm.
Но есть один маленький нюанс: по умолчанию в настройках Webpack исходный код тоже публикуется, и его любой может успешно посмотреть в Developer Tools. А в документации по Compose Multiplatform нет ни одного упоминания о том, как это предотвратить🤡
Так что обязательно отключайте публикацию сорсов в настройках Webpack на релизе. Как это сделать — смотрите в комментариях 👇
#Security #Compose #WEB
Я уже как-то писал, что никакая кроссплатформа не освобождает от необходимости знать среду выполнения, и это в очередной раз стрельнуло.
Если в Android мы переживаем за то, чтобы никто не зареверсил наш код, подключаем R8 и так далее, то как обеспечить такой же уровень защиты в вебе?
Казалось бы, там всё ок из коробки: в релизном билде что-то понять из итогового JS-файла не получится, особенно с Wasm.
Но есть один маленький нюанс: по умолчанию в настройках Webpack исходный код тоже публикуется, и его любой может успешно посмотреть в Developer Tools. А в документации по Compose Multiplatform нет ни одного упоминания о том, как это предотвратить
Так что обязательно отключайте публикацию сорсов в настройках Webpack на релизе. Как это сделать — смотрите в комментариях 👇
#Security #Compose #WEB
Please open Telegram to view this post
VIEW IN TELEGRAM
❤28👍18🤬4🔥2