Compose Broadcast
5.92K subscribers
356 photos
91 videos
585 links
Все о Jetpack Compose и Compose Multiplatform

YouTubе канал: https://youtube.com/androidBroadcast
Android - @android_broadcast
iOS - @ios_broadcast
Kotlin - @kotlin_broadcast
Download Telegram
📹 Полный обзор Ultron - UI тесты для Compose Multiplatform (3 часа) и версия на 📹 VK Video

Обзор фреймворка Ultron для написания UI тестов на Android. Если пишите чисто нативно под Android на Kaspresso, то переход дело вкуса, но вот именно на Compose Multiplatform открывается вся мощь!

🔗 Обсуждение технологии в чате @ultron_framework

8:13 Стандартный фреймворк для UI тестов
16:00 Kaspresso
18:48 Тесты на Ultron
24:28 Сравнение с Google и Kaspresso
25:25 Интеграционные Compose тесты
28:10 Пример 1. Тест на разных ферймворках
40:46 2 интеграционный тест
41:35 useUnmergedTree в Google framework
44:41 Пример 2. Тест на разных ферймворкахю
1:00:56 Ultron, индексы по всему LazyList
1:07:36 Ultron, testTag - 2 способ поиска в LazyList
1:11:41 Allure report, Ultron и Kaspresso
1:23:15 Разница в allure report между ними(Ultron и Kaspresso)
1:31:07 1 тест RecyclerView на других фреймворках, Page Object
1:35:40 Ultron, 1 тест RecyclerView, Page Object
1:39:00 2 тест RecyclerView на других фреймворках, Page Object
1:42:38 Ultron, 2 тест RecyclerView, Page Object
1:53:00 Ultron, Сравнение тестов LazyList и RecyclerView
1:54:11 Ultron, RecyclerView features
2:01:50 Ultron, UI automator, сравнение с Google
2:06:15 Ultron, тест WebView
2:11:04 Ultron listeners (и далее внутрянка)
2:14:43 Ultron extensions
2:19:55 Ultron withAssertion extension
2:23:43 Ultron performOnViewForcibly
2:26:11 Ultron, View custom extensions
2:34:04 Ultron, Compose custom extensions
2:41:16 Ultron, Rules management
2:50:48 Ultron, test data for single test

#compose #тестирование #anroid #ios #desktop
Please open Telegram to view this post
VIEW IN TELEGRAM
👍16
⚙️ Pausable composition in lazy prefetch: как Compose теперь борется с лагами при прокрутке

Важное изменение в Compose 1.10: pausable composition в lazy prefetch теперь включен по умолчанию. Это фундаментальное улучшение в работе runtime, которое значительно уменьшает лаги при сложных UI-нагрузках.

Раньше композиция, раз начавшись, должна была выполниться до конца. Если она была сложной (много элементов, тяжелые вычисления), это могло заблокировать главный поток дольше, чем длится один кадр и получали Freeze Frame и визуальные лаги скролла.

Теперь Compose Runtime может приостанавливать работу, если время на отрисовку кадра заканчивается, и продолжить её в следующем интервале. Особенно эффективно это работает в связке с предзагрузкой (prefetch) ленивых списков.

🔄 Как это работает с Lazy layouts:

// Увеличиваем окно кэша для большего пространства предзагрузки
val cacheWindow = LazyLayoutCacheWindow(
ahead = 0.5f, // 50% вперед
behind = 0.3f // 30% назад
)

val state = rememberLazyListState(cacheWindow = cacheWindow)

LazyColumn(state = state) {
items(heavyItems) { item ->
HeavyComposable(item) // Теперь не заблокирует UI
}
}


🎯 Ключевые преимущества:
1. Плавная прокрутка — даже с тяжелыми элементами
2. Композиция подстраивается под время для отрисовки кадра — композиция «уступает» место другим операциям
3. Никакой сложной настройки — не требует изменения кода приложения

Эта оптимизация — часть продолжающейся работы Google над производительностью Compose. Уже пробовали? Делитесь наблюдениями в комментариях!

#Compose #Производительность #AndroidDev #JetpackCompose
Please open Telegram to view this post
VIEW IN TELEGRAM
👍46🔥177
🎭 Динамическое управление shared element анимациями в Compose

В Compose 1.10.0 вы можете динамически включать и отключать анимации shared element в зависимости от условий навигации или состояния UI. Это особенно полезно, когда нужно анимировать переход только в определенных сценариях.

Раньше sharedElement() и sharedBounds() автоматически анимировали изменения layout при нахождения совпадению по ключу. Теперь можно контролировать эту анимацию через конфигурацию SharedContentConfig.


// отим анимировать переход только с экрана A на экран B, но не обратно
SharedTransitionLayout {
val transition = updateTransition(currentState)

transition.AnimatedContent { targetState ->
// Конфигурация, зависящая от состояния
fun animationConfig(): SharedTransitionScope.SharedContentConfig {
return object : SharedTransitionScope.SharedContentConfig {
override val SharedTransitionScope.SharedContentState.isEnabled: Boolean
get() = transition.currentState == "A" &&
transition.targetState == "B"
}
}

...
}
}


⚠️ Важно: По умолчанию, если shared element отключается во время анимации, текущая анимация завершается до удаления элемента. Это предотвращает резкие обрывы.

Новая фича даёт разработчикам больше контроля над анимациями, делая интерфейсы более предсказуемыми и оптимизированными.

#Compose #AndroidDev #Анимация #UI
👍9
⚙️ Новый API `retain` в Compose: сохраняем сложные объекты при смене конфигурации

Compose 1.10 представляет новую функцию retain, которая заполняет важный пробел между существующими API управления состоянием. Теперь можно сохранять объекты между изменениями конфигурации без необходимости их сериализации!

- remember — сохраняет между рекомпозициями смена конфигурации
- rememberSavable — сохраняет между пересозданиями активити ⚠️ требует сериализации
- retain — сохраняет при смене конфигурации без сериализации не работает при убийстве процесса

@Composable
fun MediaPlayer() {
val applicationContext = LocalContext.current.applicationContext

// ExoPlayer будет сохранен при повороте экрана
val exoPlayer = retain {
ExoPlayer.Builder(applicationContext)
.setSeekBackIncrementMs(5000)
.setSeekForwardIncrementMs(5000)
.build()
}

// Воспроизведение не прервется при смене конфигурации
DisposableEffect(Unit) {
onDispose { exoPlayer.release() }
}
// ...
}


Под капотом сохранение объекта происходит через механизм ViewModel и имеет такой же цикл жизни

Фича разработана при активном участии AndroidDev-сообщества, особенно команды Circuit. Отличный пример того, как обратная связь разработчиков влияет на развитие платформы!

#Compose #AndroidDev
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥65👍168👎1
Forwarded from Android Broadcast
🤯 Конец Android View ближе чем кажется - в будущей версии Android Studio убирают поддержку превью для Custom View.

Источник - сайт Android Developers

#Android #AndroidDev #Compose #AndroidStudio
👍60🤯42👎18🔥13🎉11🏆1
⚙️ Dejavu — тесты на рекомпозицию в Compose

Мэтт МакКенна выпустил библиотеку, которая превращает рекомпозиции в обычные test assertions. Dejavu решает проблемы постоянного мониторинга за рекомпозияцими без изменений в продакшн-коде — только Modifier.testTag(), который скорее всего у вас уже есть:

// Пример теста
@get:Rule
val composeTestRule = createRecompositionTrackingRule()

@Test
fun incrementCounter_onlyValueRecomposes() {
composeTestRule.onNodeWithTag("inc_button").performClick()

composeTestRule.onNodeWithTag("counter_value")
.assertRecompositions(exactly = 1)

composeTestRule.onNodeWithTag("counter_title")
.assertStable() // ноль рекомпозиций
}


Когда тест падает, получаете структурированный отчёт:
UnexpectedRecompositionsError: testTag='product_header'
Composable: demo.app.ui.ProductHeader (ProductList.kt:29)
Expected: exactly 0 recomposition(s)
Actual: 1 recomposition(s)

All tracked composables:
ProductListScreen = 1
ProductHeader = 1 <-- FAILED
ProductItem = 1

Recomposition timeline:
#1 at +0ms — param slots changed: [1] | parent: ProductListScreen

Possible cause:
1 state change(s) of type Int
Parameter/parent change detected (dirty bits set)


Видно какой composable, сколько раз рекомпозировался и почему. Под капотом используется CompositionTracer API из compose-runtime 1.2.0, никаких Gradle-плагинов и байткод-манипуляций. Запускается как instrumented тест.

🐱 Dejavu Github

#Android #AndroidDev #Compose #JetpackCompose
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥48👏75👎4🤔2👍1
This media is not supported in your browser
VIEW IN TELEGRAM
⚙️ Анимации в Jetpack Compose LazyColumn: почему это сложнее, чем кажется

Наткнулся на отличный разбор от ребят из Т-Банка — они переписывали главный экран с XML+View на Compose и столкнулись с проблемой, которую многие, думаю, обходят стороной.

Суть: есть LazyColumn, в нём элемент с animateContentSize(). Когда элемент расширяется по высоте, нижние карточки не успевают сместиться в такт — небольшой, но заметный рассинхрон.

Казалось бы — поменяй spring на tween, синхронизируй тайминги и готово. Спойлер: нет.

1️⃣ Замена на линейный tween — разницы визуально почти ноль. Скорость placementSpec тоже не влияет на смещение нижних айтемов при ресайзе верхнего.

2️⃣ Установка placementSpec = null — синхронизация появляется, но полностью ломает анимацию перемещения айтемов. Не вариант.

3️⃣ Попытка написать свой модификатор — обречена с самого начала. LazyLayoutAnimationSpecsNode помечен как internal, а внутренний LazyLayoutItemAnimator, который реально управляет анимациями, недоступен снаружи. Скопировать код не выйдет — каст по типу вернёт null, и вся механика рассыпается. Форкать 5000+ строк LazyColumn — очевидно нет.

Самое интересное — анимация удаления при кастомной реализации не работает в принципе: к моменту DisposableEffect.onDispose элемент уже удалён из дерева. А стандартный animateItem работает на уровне layout-фазы и может буквально «удерживать» элемент в дереве до окончания анимации.

Итог у команды — оставили RecyclerView для списка, айтемы внутри на Compose. Костыль, но рабочий, пока Google не откроет нужные API. Issue уже создан, подписывайтесь если сталкивались.

Я лично не сталкивался с таким кейсом в продакшне, но статья хорошо показывает, где у Compose сейчас реальные границы расширяемости и что без View пока никуда.

#Android #Compose #AndroidDev #JetpackCompose #Анимация
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥37👎138👍2
⚙️ Rebound — бюджеты рекомпозиций для Compose

Одна из моих любимых тем в Compose-разработке — отладка рекомпозиций. Layout Inspector, Rebugger, ComposeInvestigator — всё это хорошие инструменты, но у них общий слепой угол: они говорят сколько рекомпозиций, но не отвечают на вопрос нормально ли это для данного компонента.

HomeScreen, который рекомпозируется 10 раз в секунду — это проблема. Анимация, которая делает то же самое — это норма. Число одинаковое, вывод противоположный.

Библиотека работает на основе Kotlin compiler plugin, который классифицирует каждый @Composable по роли и назначает ему бюджет рекомпозиций:

1️⃣ Screen — 3/s. Если экранный компонент рекомпозируется чаще, state утекает вверх по дереву
2️⃣ Leaf — 5/s. Text, Icon, Image — дёшевы сами по себе, но не должны «молотить»
3️⃣ Animated — 120/s. Всё, что использует animate*, Transition, Animatable — пусть работает
4️⃣ Container — 10/s, Interactive — 30/s, List Item — 60/s

При скролле бюджеты удваиваются, при анимации и вводе — умножаются на 1.5. Контекст учитывается.

Когда что-то выходит за рамки, в логах появляется не просто число, а конкретика:

BUDGET VIOLATION: ProfileHeader rate=11/s exceeds LEAF budget=5/s
-> params: avatarUrl=CHANGED, displayName=CHANGED
-> forced: 0 | param-driven: 11 | interaction: IDLE

🔨 Плюс IDE-плагин с live-деревом, таблицей горячих точек, timeline-хитмапой, и самое полезное — цветными иконками прямо в редакторе. Зелёный/жёлтый/красный кружок рядом с каждым @Composable. Никакого переключения контекста.

Попробую на своих проектах (только не отправляй в прод) — идея с контекстными бюджетами кажется мне намного честнее, чем единый порог для всех компонентов. Решение пока не достигло версии 1.0 но это и некритично, так как не влияет на продакшен код.

🔗 Источник: adital.dev
🐱 Исходники на GitHub

#Android #Compose #AndroidDev #Производительность
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
49👍13👎8
Media is too big
VIEW IN TELEGRAM
🐱 Tracey - чёрный ящик для Compose-приложения

Tracey записывает все жесты, переходы между экранами и кастомные события в кольцевой буфер — и при краше сохраняет его для воспроизведения. Видишь не просто стектрейс, а весь путь пользователя до момента падения.

Не сразу понял куда её применить, но пришла идея интеграции в флоу автоматического прокликивания экрана:

Разрабатываешь фичу локально, кликаешь руками, что-то идёт не так. Вместо того чтобы объяснять разработчику или агенту на словах "я нажал сюда, потом перешёл туда, потом кнопка не сработала" — просто скидываешь ему дамп сессии из Tracey. Он сам восстанавливает картину и сразу работает с контекстом, а не с твоим пересказом.


Структурированный контекст для дебаг-сессии с агентом, чтобы дать четкую информацию.

Библиотека на версии 0.0.2, только вышла, в продакшен пока не потащу. Но для этапа разработки и связки с AI-агентами идея выглядит рабочей.

#Compose #Android #AndroidDev
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥19👍5👎5
🐱 Kompass — ещё одна навигация для Compose Multiplatform

Да, у нас уже есть Jetpack Navigation 3 с официальной поддержкой KMP. Тем не менее авторы Kompass смотрят на проблему иначе, и идея тут любопытная.

Суть в том, что вся навигация строится на чистых редукторах. Любой переход — это State + Command → State, без побочных эффектов. Вот как это выглядит в коде:

// Граф описывает, какие экраны в нём живут и как рендерятся
class MainNavigationGraph : NavigationGraph {
override fun canResolveDestination(id: String) =
id in setOf("home", "profile")

@Composable
override fun Content(entry: BackStackEntry, destination: Destination, navController: NavController) {
when (destination) {
is MainDestination.Home -> HomeScreen(navController)
is MainDestination.Profile -> ProfileScreen(navController)
}
}
}

// Хост принимает список графов и рендерит текущий экран
@Composable
fun AppNavigation() {
val navController = rememberNavController(
startDestination = MainDestination.Home
)
KompassNavigationHost(
navController = navController,
graphs = persistentListOf(MainNavigationGraph())
)
}

// Навигация из экрана
navController.navigate(
entry = BackStackEntry(destinationId = "profile", scopeId = newScope())
)

// Возврат с результатом
navController.pop(result = ProfileResult(userId = "123"))


NavigationState и BackStackEntry иммутабельны, поэтому всю навигацию можно покрыть обычными unit-тестами без инструментации: создаёшь NavigationHandler, кидаешь команду, проверяешь стейт.

Особенности:
👉 Таргеты — Android, iOS, Desktop (JVM).
👉 Scopes вместо ViewModel. rememberScoped<T> живёт ровно пока BackStackEntry в стеке, автоматически чистится при pop.
👉 Multi-graph. Несколько независимых графов с собственными лейаутами. Из коробки есть поддержка master-detail для планшетов.
👉 Дип-линки. Через DeepLinkHandler — типизированный парсинг URI в NavigationCommand.
🛠 Библиотека ещё в активной разработке

#KMP #ComposeMultiplatform #Navigation #Kotlin #AndroidDev
Please open Telegram to view this post
VIEW IN TELEGRAM
👎30👍9