Compose Broadcast
5.9K subscribers
374 photos
95 videos
616 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
21👍3
🎭 Динамическое управление 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
⚙️ Создание кастомного Toolbar в Jetpack Compose: от проблемы к решению

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

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

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

#Compose #Android #UI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15🤔3👎1
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
🔥113👍86👎1
⚙️ 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
37🔥27👍3👎2
⚙️ Вышел 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
👍25👎2
Forwarded from Android Broadcast
⚙️ Compose Remote уже в Альфа. Шаг за шагом выходят версии и BDUI на Compose уже в пути! Подробнее писал тут

#Android #Compose #BDUI #SDUI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍34👎73🔥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
⚙️ 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
👍17👎2
⚙️ Кажется, проблема с обёртками для 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
👍55👎5
⚙️ Новинка 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
👍33👎2
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
⚙️ Мигрировать на 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
👎32👍22
⚙️ Вышла стабильная версия 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
👍25👎4
⚙️ 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
1👍15👎6
⚙️ Compose Remote добрался до первой Альфы. Это реализация BDUI (верстка приходит с сервера) на основе Compose от Google!

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

#Compose #Android #Remote #BDUI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍46👎8