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

По сотрудничеству - @ab_manager
Download Telegram
⚙️ Вышел Jetpack Compose 1.10.0

👉 Стабильное API Shared Transition
👉 Оптимизированный скролл
👉 Новые подходы к сохранению данных при пересоздании Activity через ViewModel
🚀 Повышена производительность UI на Compose
🛠 Исправлено багов и шероховатостей

Изменений действительно много — в один пост всё не поместить.

Буду разбирать ключевые обновления по отдельности в следующих публикациях на @compose_broadcast

#compose #android
Please open Telegram to view this post
VIEW IN 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
⚙️ Создание кастомного Toolbar в Jetpack Compose: от проблемы к решению

Разработчик из ПСБ поделился опытом создания кастомного Toolbar в Compose. Основная задача — правильное центрирование заголовка и подзаголовка при динамическом контенте слева и справа (иконки, текст переменной длины).

Решения "в лоб" не сработали:
- Row с Weight приводит к лишним рекомпозициям
- Ручной расчет ширины текста — непредсказуемо и сложно
- Проблема в разных фазах измерения Compose

Решением стал кастомный Layout. Вместо стандартных компоновок используется Layout, который измеряет все элементы за один проход.

#Compose #Android #UI
Please open Telegram to view this post
VIEW IN TELEGRAM
This media is not supported in your browser
VIEW IN TELEGRAM
🛠 Riflesso - плагин для IDEA с показом областей кода, где происходит рекомпозиция в реальном времени. Поддерживается Android и Desktop таргеты

📃 Подробнее про реализацию плагина автор рассказал в статье (RU,30м)

#Compose #IDEA #AndroidStudio #Android #Desktop
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ Compose Multiplatform 1.10.0 — крупнейший релиз года! (ну он пока единственный)

Привет! Вышла стабильная версия Compose Multiplatform 1.10.0 с революционными изменениями для кроссплатформенной разработки. Давайте разберём главные фичи, которые изменят ваш подход к KMP.

🔥 Главная фишки релиз - Preview аннотация теперь работает в коде из commonMain — наконец-то!

// Работает в commonMain!
@Preview
@Composable
fun MyComponentPreview() {
MyTheme {
MyComponent()
}
}


МИГРАЦИЯ! Депрекейт org.jetbrains.compose.ui.tooling.preview.Preview → переходите на androidx.compose.ui.tooling.preview.Preview

👉 Compose Hot Reload встроен в Compose плагин и подключать отдельно больше не нужно
⚙️ Базовая поддержка библиотеки navigation3 и публикация adaptive-navigation3
🍎 iOS: Поддержка Liquid Glass и полная поддержка WindowInsets
⚙️ Улучшили производительность Compose Desktop

⚠️ Breaking Changes & Миграция

Требования к версиям Kotlin
- Kotlin 2.1.20+ (обязательно из-за Hot Reload)
- Kotlin 2.2+ для native и web платформ

🐘 Депрекейт dependency aliases в Gradle plugin → используйте координаты артефактов из Maven
// build.gradle.kts
kotlin {
sourceSets {
commonMain.dependencies {
implementation(compose.ui)
// Заменяем на
implementation("org.jetbrains.compose.ui:ui:1.10.0")
}
}
}


Кто уже обновился? Какие впечатления от Preview в commonMain? Делитесь в комментариях!

#compose #gradle #kmp #android #ios #desktop
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ Вышел Jetpack Compose 1.10.1

Обновляйте зависимости или сразу всё через BOM файл:
dependencies {
implementation(
platform("androidx.compose:compose-bom-beta:2026.01.00")
)
}

#compose #android
Please open Telegram to view this post
VIEW IN TELEGRAM
Forwarded from Android Broadcast
⚙️ Compose Remote уже в Альфа. Шаг за шагом выходят версии и BDUI на Compose уже в пути! Подробнее писал тут

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

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

#Android #AndroidDev #Compose #AndroidStudio
⚙️ 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
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
⚙️ 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
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
⚙️ Jetpack Compose 1.11 — стабильный релиз! 🚀

Что нового:
👉 Визуальная отладка переходов Shared Elements
👉 Переработанная поддержка трекпадов — теперь работают как мышь и распознают жесты
👉 Preview Wrappers для более быстрой работы с превью
👉 Host Defaults для KMP-проектов
👉 Testing APIs v2 для корутин
👉 Экспериментальные API: Styles, MediaQuery, Grid, FlexBox

Подробности по каждому пункту — отдельными постами в @compose_broadcast

#Compose #Android #AndroidJetpack
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ Кажется, проблема с обёртками для Preview в Compose наконец-то решена нормально — без копипаста на каждый файл.

В Compose 1.11.0 завезли новый интерфейс PreviewWrapper и аннотацию @PreviewWrapperProvider. Идея простая: вместо того чтобы оборачивать каждый Preview в свою тему, CompositionLocalProvider, моки навигации и прочее, ты один раз описываешь обёртку и просто навешиваешь её аннотацией.

Выглядит это так:
class ThemeWrapper : PreviewWrapper {
@Composable
override fun Wrap(content: @Composable () -> Unit) {
JetsnackTheme {
content()
}
}
}

@PreviewWrapperProvider(ThemeWrapper::class)
@Preview
@Composable
private fun ButtonPreview() {
Button(onClick = {}) {
Text("Demo")
}
}


Сценарии где может пригодиться:
👉 встроить тему в превью
👉 подложить FakeNavController или LocalActivity
👉 обернуть в Hilt/Koin провайдер с тестовыми зависимостями
👉 прокинуть моки через CompositionLocal

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

‼️ ВАЖНО: фича работает только в Android Studio Panda 4 и выше. В более старых версиях аннотация просто проигнорируется.

💬 Уже успели опробовать? Делитесь впечатлениями и насколько жизнь стала удобнее

#compose #jetpackcompose #android
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ Новинка Compose 1.11: неленивый Grid

До сих пор для сеток у нас были только ленивые LazyVerticalGrid / LazyHorizontalGrid. Но если все элементы уже в памяти (небольшое количество) и нужен точный контроль над расположением ячеек — пригождается новый экспериментальный Grid.

// Пример
Grid(config = {
column(100.dp)
column(1.fr) // забирает остаток строки
row(50.dp)
row(80.dp)
gap(8.dp)
}) {
Box(Modifier.gridItem().fillMaxSize()) { ... }
Box(Modifier.gridItem().fillMaxSize()) { ... }
Box(Modifier.gridItem().fillMaxSize()) { ... }
Box(Modifier.gridItem().fillMaxSize()) { ... }
}


Работает на Android и KMP (iOS, Desktop, Web) – общий API

🔗 Документация по Grid

#Compose #KMP #Android #iOS #Desktop #Web #CMP
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
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
⚙️ Мигрировать на Navigation 3 можно, но только всё приложение разом. И это не единственная засада

Честный кейс от разработчицы: она в одиночку перевела приложение на 170+ экранов с Nav2 на Nav3 🤯. Не пересказ доков, а грабли из прода. Объем работы: 302 файла (по сути весь app), 2 недели разработки с овертаймами, ~1.5 недели стабилизации, 1 хотфикс.

Что стоит забрать себе ещё до старта:

Поэтапно не выйдет. Обратной совместимости между Nav2 и Nav3 нет, мигрируешь либо всё сразу, либо никак. Отсюда огромный affected area и главная организационная боль: команда не фризила разработку, коллеги параллельно пилили новые экраны на старой навигации. Чем дольше тянешь, тем дороже разгребать merge-конфликты.

Bottom Sheets из коробки в навигации нет. Пришлось городить обёртку вокруг entry<> с флагом в metadata и вручную разруливать, что рисовать через ModalBottomSheetLayout, а что через NavDisplay:

val NavEntry<out NavKey>.isBottomSheet: Boolean
get() = metadata["isBottomSheet"] as? Boolean == true

NavDisplay(
entries = navigationState.toEntries(mainEntryProvider)
.filter { it.isBottomSheet.not() },
onBack = { navigator.goBack() },
)


Сейчас это решается официально через BottomSheetSceneStrategy, свой костыль автор потом выкинула. Мигрируешь сегодня — сразу проверь, что шторки ложатся на SceneStrategy, а не переоткрывай этот велосипед. ⚠️ И отдельный подвох: шторка часть стека, и если перед переходом на следующий экран не закрыть её руками, back вернёт её обратно. Легко забыть.

Сериализация уронит тебя уже в проде. 💥 Всё, что прокидываешь через навигацию, должно быть @Serializable. Автор прокинула entryPoint: NavKey (чтобы при блокировке юзера возвращать на нужный экран через несколько назад), а сам NavKey сериализуемым не был:

Fatal Exception: kotlinx.serialization.SerializationException


Ловушка в том, что краш не в обычном флоу, а в отдельных сценариях восстановления, поэтому проходит ревью и ручные тесты, а ловится только у юзеров.

🔗 Полный разбор с кодом и схемами на Medium

#Compose #Navigation3 #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ Вышла стабильная версия Jetpack Compose 1.12.0! CMP уже подходе

Для обновления просто обновите BOM
implementation(platform("androidx.compose:compose-bom:2026.08.00"))


⚠️ Важно: требуется compileSdk = 37 и AGP 9.2.0+.

В этом релизе:
🎨 Mesh‑градиенты и HDR
📐 Именованные области в Grid
✍️ Редактируемое форматирование текста
🎬 Двухстадийные анимации
⚡️ Ускоренный старт и Keyed SideEffect
🔧 Не обошлось без breaking changes

В ближайших постах разберу каждую фичу подробно с примерами. Подпишись на @ComposeBroadcast, чтобы ничего не пропустить!

Compose Multiplatform 1.12.0 уже на стадии RC

#Compose #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ mediaQuery в Compose: один вызов вместо кучи WindowSizeClass + posture + input

Официально появился mediaQuery — лямбда, которая смотрит на текущий UiMediaScope и возвращает boolean. Внутри можно проверять:
👉 windowWidth / windowHeight
👉 windowPosture (Tabletop, Book, Flat)
👉 pointerPrecision (Fine → Coarse → Blunt → None)
👉 keyboardKind (Physical / Virtual / None)
👉 hasCamera / hasMicrophone
👉 viewingDistance (Near / Medium / Far)

Включается одним флагом при запуске приложения:
ComposeUiFlags.isMediaQueryIntegrationEnabled = true


Для размеров, которые дёргаются часто, лучше использовать derivedMediaQuery (обёртка над derivedStateOf):
// Composable code
val narrowerThanMedium by derivedMediaQuery {
windowWidth < WindowSizeClass.WIDTH_DP_MEDIUM_LOWER_BOUND.dp
}
val narrowerThanExpanded by derivedMediaQuery {
windowWidth < WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND.dp
}
when {
narrowerThanMedium -> SinglePaneLayout()
narrowerThanExpanded -> TwoPaneLayout()
else -> ThreePaneLayout()
}


Самый чистый кейс — foldable в tabletop:
// Composable code
if (mediaQuery { windowPosture == UiMediaScope.Posture.Tabletop }) {
TabletopLayout()
} else {
FlatLayout()
}


Это API не замена WindowSizeClass, но удобный единый вход для всего, что меняется в рантайме. Если пишете адаптив под foldable / desktop / TV — возьмите на заметку. Пока не перенесли в Compose Multiplatform.

Документация Media Query API

#Compose #JetpackCompose #Adaptive #UI #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
⚙️ Compose Remote добрался до первой Альфы. Это реализация BDUI (верстка приходит с сервера) на основе Compose от Google!

💬 Нужно ли оно вам?

#Compose #Android #Remote #BDUI
Please open Telegram to view this post
VIEW IN TELEGRAM