Compose Broadcast
5.9K subscribers
373 photos
95 videos
613 links
Все о Jetpack Compose и Compose Multiplatform

По сотрудничеству - @ab_manager
Download Telegram
🎭 Динамическое управление 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
👍61🤯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
🔥49👏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
🔥20👍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
👎31👍9
Forwarded from Android Broadcast
‼️ Android View всё! Да здравствует Compose!

Google официально объявила о переходе к стратегии «Compose-first» и прекращении развития Android View, переводя его в режим поддержки.

Что это значит:
👉 Классы android.widget.* в Android SDK будут находиться в режиме поддержки.
👉 Библиотеки Android Jetpack (включая RecyclerView) также переходят в режим поддержки.
👉 Все инструменты в Android Studio будут создаваться только для Compose.
👉 Документация будет писаться только для Compose, однако некоторые разделы про View останутся.

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

#Android #AndroidDev #Compose
👍103👎21
This media is not supported in your browser
VIEW IN TELEGRAM
⚙️ skydoves выпустил Compose HotSwan v2 Beta для Android: заменить весь экран без рестарта приложения

v1 умел одно — применять изменения значений без rebuild: цвет, padding, текст, размеры — результат за секунду, приложение живёт. Добавить composable, переставить layout, поменять control flow — всё это шло в полный rebuild.

v2 убирает эту границу. Без рестарта приложения: добавляем и удаляем composable-функции, меняем структуру layout, переписываем conditional UI. И главное — заменяем целый экран на совершенно другой код. Навигационный стек и remember-состояние сохраняются.

Технически v2 запускает изменения через собственный интерпретатор, обходя ограничения ART на переопределение классов — тот же подход, что у Flutter. При слишком глубоких изменениях деградирует плавно: сбрасывает composition с сохранением rememberSaveable и ViewModel, в крайнем случае рестартит Activity. Полный rebuild нужен только при изменении сигнатур функций, иерархии классов и inline-функций.

В v2 также MCP-сервер: AI-агент триггерит reload, снимает скриншот с устройства, итерирует по вариантам. Claude Code и Cursor работают из коробки через file watcher.

Поставить: IDE-плагин вручную с бета-канала Marketplace + Gradle-плагин 2.0.0-beta01:

[plugins]
hotswan-compiler = { id = "com.github.skydoves.compose.hotswan.compiler", version = "2.0.0-beta01" }


Инструмент платный инструмент, лицензия и цены на hotswan.dev/pricing.

Такого охвата hot reload в нативном Compose ещё не было. Аргумент Flutter про быстрый feedback loop заметно слабеет.

#Compose #JetpackCompose #AndroidDev
Please open Telegram to view this post
VIEW IN TELEGRAM
👎23👍14
⚙️ Новый релиз Material3 Adaptive 1.3.0

Новая библиотека adaptive-navigation3. Теперь можно легко строить адаптивные Material-интерфейсы в связке с Navigation 3. Что внутри:

Scene Strategies
👉 ListDetailSceneStrategy + rememberListDetailSceneStrategy — канонический list-detail
👉 SupportingPaneSceneStrategy + rememberSupportingPaneSceneStrategy — supporting pane
👉 В ListDetailPaneScaffold и SupportingPaneScaffold. Теперь поддерживают margins и edge-to-edge.
🔄 RTL-поддержка для pane expansion.
tive:adaptive-navigation3:1.3.0")

🔗 Полный changelog

#AndroidDev #JetpackCompose #Material3
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7👎4