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
⚙️ 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
⚙️ Запрещаем composable вне превью через @RequiresOptIn

Если composable нужен только для превью из другого модуля, internal не поможет. Зато поможет кастомная аннотация поверх @RequiresOptIn с уровнем ERROR.

kotlin@PreviewOnly
@Composable
fun renderScreenPreviewer(list: List) {
renderList(list)
}

@RequiresOptIn(
message = "This composable is intended for preview usage only",
level = RequiresOptIn.Level.ERROR
)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION)
annotation class PreviewOnly


Теперь любой, кто попробует вызвать его в продакшн коде без @OptIn(PreviewOnly::class), получит ошибку компиляции, а не warning.

Небольшой минус: @OptIn(PreviewOnly::class) придётся писать в каждой превью-функции вручную, автоматического opt-in для @Preview нет. Но это копейки по сравнению с тем, что превью-composable физически не попадёт в продакшн.

Источник le0nidas.gr

#Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
👍36👎7
This media is not supported in your browser
VIEW IN TELEGRAM
HotSwan - быстрое обновление Composable на реальном устройстве. Доступно как плагин для Android Studio

Я выбрал другой путь - делаю СMP проект с поддержкой Android + Desktop JVM и так можно быстро проверять + делать код чище.

#Compose
👍16👎9
⚙️ Navigation3 1.1.0 стабилен

Самое интересное из нового: поддержка Shared Elements между экранами. Теперь достаточно обернуть NavDisplay в SharedTransitionLayout и передать scope:

SharedTransitionLayout {
NavDisplay(
backStack = backStack,
sharedTransitionScope = this
)
}


Второе большое добавление — SceneDecoratorStrategy. Отдельный интерфейс для декорирования сцен общими UI-элементами или для шаринга состояния между ними. Логика декорирования теперь отделена от логики выбора сцены в SceneStrategy.

Type safe metadata DSL через MetadataKey. Больше не нужны строковые ключи — типы значений описаны статически. Готовые NavMetadataKey позволяют задавать анимации переходов прямо в entryProvider:

entry<ScreenC>(
metadata = metadata {
put(NavDisplay.TransitionKey) {
slideInVertically { it } togetherWith fadeOut()
}
}
) { ... }


#compose #navigation3 #jetpack
Please open Telegram to view this post
VIEW IN TELEGRAM
👍58👎3
⚙️ 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
👍16👎1
⚙️ Кажется, проблема с обёртками для 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
👍53👎4
This media is not supported in your browser
VIEW IN TELEGRAM
⚙️ Отладка shared element анимаций в Compose 1.11 стала проще. Появилась функция LookaheadAnimationVisualDebugging, визуализирующая анимацию

#Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
👍28👎2
// Пример использования
LookaheadAnimationVisualDebugging(
overlayColor = Color(0x4AE91E63),
isEnabled = true,
multipleMatchesColor = Color.Green,
isShowKeylabelEnabled = false,
unmatchedElementColor = Color.Red,
) {
SharedTransitionLayout {
CompositionLocalProvider(
LocalSharedTransitionScope provides this,
) {
// your content
}
}
}


#Compose
👍12👎4
🤖 Styles API в Compose: революция в кастомизации UI

До недавнего времени стилизация компонентов в Compose делалась через параметры Composable функции (color = ..., padding = ...) или модификаторы. Это работало, но могло приводить к:
👉 избыточным рекомпозициям при смене состояний;
👉 дублированию кода для разных вариаций кнопок/карточек;
👉 сложностям с анимацией переходов между стилями.

Google представил Styles API (экспериментальный, о причинах такого статуса ниже) — новый подход, вдохновлённый CSS. Нужен Jetpack Compose 1.11.0 или свежее

Ключевые возможности
1️⃣ Производительность
Стили вычисляются на этапах layout и drawing, минуя composition. Это значит, что изменение стиля (например, при наведении) не вызывает рекомпозицию родительских компонентов.

2️⃣ Работа с состояниями
Без кучи if/else и animate*AsState. Просто описываете стиль для каждого состояния (нажат, сфокусирован, включён, ошибка и т.д.).

3️⃣ Встроенные анимации
Переходы между состояниями анимируются автоматически с физическими параметрами (демпфирование, жёсткость). Не нужно писать Animatable или Transition.

// Пример использования стиля
// Определяем стиль для кнопки
val buttonStyle = Style {
default {
backgroundColor = Color.Blue
shape = RoundedCornerShape(8.dp)
}
pressed {
backgroundColor = Color.DarkBlue
scale = 0.98f
}
disabled {
backgroundColor = Color.Gray
alpha = 0.5f
}
}

// Используем
Button(
onClick = { /* ... */ },
style = buttonStyle, // ← вместо кучи параметров
enabled = isEnabled
) {
Text("Нажми меня")
}


style — новый параметр, который появляется у всех компонентов Material 3 и базовых (Box, Row, Column).

⚠️ Styles API не заменяет модификаторы
Модификаторы остаются для позиционирования, кликов, размеров. Styles API заменяет только внутреннюю стилизацию (цвета, формы, тени, отступы внутри компонента). Это как разделение «темы» и «вёрстки».

API остаётся экспериментальным пока на него полностью не перейдет Material 3 Compose, чтобы учесть все необходиcмоти API и не делать breaking changes. По работе оно уже стабильно, но вот изменения в коде, возможно, будут.

🔗 Официальная документация

В целом с таким подходом уже привыкли работать в Android XML Layout, то почему бы сразу было его не использовать? Тем более в Compose плюсы очевидны!

💬 Пробовали уже играться со Styles API? Как вам идея отделить стили от композиции? Делитесь мнениями в комментариях 👇

#Compose #Material
Please open Telegram to view this post
VIEW IN TELEGRAM
👍80👎2
⚙️ Новинка 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
👍29👎1
Media is too big
VIEW IN TELEGRAM
⚙️ splashify - библиотека для интеграции splash экрана в приложении на Compose Desktop

#Compose #Desktop
Please open Telegram to view this post
VIEW IN TELEGRAM
👍22👎6
This media is not supported in your browser
VIEW IN TELEGRAM
⚙️ FlexBox Layout в Jetpack Compose 1.11.0 - построение динамической сетки из компонентов

В составе androidx.compose.foundation:layout 1.11.0 появился компонент FlexBox. Компоновка на основе правил CSS Flexbox. Поддерживает перенос элементов (wrap), веса (weight), выравнивание по главной и поперечной осям. Сразу вышел с поддержкой Compose Multiplatform.

FlexBox(
modifier =
Modifier.fillMaxWidth(),
config = {
flexDirection = Row
flexWrap = Wrap
justifyContent = SpaceBetween
}
) {
repeat(items.size) { index ->
Text(
text = items[index],
modifier =
Modifier.weight(1f)
)
}
}


🔗 Анонс в блоге Android Developers
🔗 API Reference

#Compose #CMP
Please open Telegram to view this post
VIEW IN TELEGRAM
👍51👎1