Compose Broadcast
5.88K subscribers
353 photos
90 videos
577 links
Все о Jetpack Compose и Compose Multiplatform

YouTubе канал: https://youtube.com/androidBroadcast
Android - @android_broadcast
iOS - @ios_broadcast
Kotlin - @kotlin_broadcast
Download Telegram
⚙️ Landscapist - новая библиотека для загрузки изображений в Compose Multiplatform

Landscapist — это модульная библиотека для загрузки изображений, построенная специально для Compose. В отличие от монолитных решений, она предлагает гибкую архитектуру с поддержкой различных движков загрузки: Glide, Coil и Fresco.

Ключевые фичи:
👉 Compose Multiplatform из коробки. Библиотека изначально проектировалась для работы на Android, iOS, Desktop и Web. Один API для всех платформ.
👉 Модульная архитектура. Вы подключаете только то, что нужно. Core-модуль весит минимум, а специфичные функции (placeholder, эффекты, анимации) добавляются отдельными зависимостями.
👉 Декларативные модификаторы. Все возможности библиотеки доступны через Compose-модификаторы, что делает код чище и понятнее.
👉 Продвинутая обработка состояний. Встроенная поддержка loading, success, failure с возможностью кастомизации под каждое состояние.
👉 Эффекты и анимации. Circular Reveal, crossfade, shimmer-эффект — всё это доступно out-of-the-box.
👉 Palette API. Автоматическое извлечение цветовой палитры из изображения для создания адаптивного UI.

suspend fun loadImage(url: String) {
val request = ImageRequest.builder()
.model(url)
.size(width = 800, height = 600)
.build()

landscapist.load(request).collect { result ->
when (result) {
is ImageResult.Loading -> {
println("Loading...")
}
is ImageResult.Success -> {
val imageBitmap = result.data
val dataSource = result.dataSource // MEMORY, DISK, or NETWORK
println("Loaded from: $dataSource")
}
is ImageResult.Failure -> {
val error = result.reason
println("Error: ${error.message}")
}
}
}
}


🐱 Исходный код
📃 Документация

#Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
👍385👎4🤔2
Forwarded from Android Live 🤖
Styles API в Jetpack Compose 🚀

Интересного завезли. В Compose появился экспериментальный API для работы со стилями, который делает их изменение гораздо удобнее.

Сейчас работа с динамическими стилями требует немало ручного труда. И хотя InteractionSource неплохо приспособлен для этих задач, Styles API упрощает процесс в разы.

Ниже приведён пример кнопки, которая меняет цвет при состояниях hovered и pressed.


@Composable
fun InteractiveButton(onClick: () -> Unit) {
ClickableStyleableBox(
onClick = onClick,
style = {
background(Color.Green)
size(150.dp)
hovered { animate { background(Color.Yellow) } }
pressed { animate { background(Color.Red) } }
}
)
}


Выглядит неплохо, посмотрим, что будет дальше. Детали тут.
24🔥13👍4👎2
This media is not supported in your browser
VIEW IN TELEGRAM
⚙️ Пример реализации подобной анимации в Compose. Весь подход выделили в библиотеку 🐱 ThemeAnimator, которую можете подключить к себе.

Анимация смены темы реализована через перехват отрисовки в Modifier.Node: сначала делается снимок UI в старой теме, затем тема переключается, фиксируется новое состояние и запускается анимация между двумя скриншотами. Переход рисуется как круговое раскрытие новой темы, что позволяет избежать мерцаний и добиться плавного эффекта.

#Compose #Анимация
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥87👍115👎5
This media is not supported in your browser
VIEW IN TELEGRAM
⚙️ Compose Stability Analyzer 0.7.0 🔥

Вышел крупный релиз плагина для Android Studio. Две главные фичи:

👉 Recomposition Cascade Visualizer
Правый клик на любой @Composable → "Analyze Recomposition Cascade" — и получаешь дерево всех downstream-компонентов, которые будут перерисованы. Для каждого показывается статус (skippable / non-skippable), общая статистика и максимальная глубина. Двойной клик по узлу — переход к исходнику. Работает с защитой от циклов и ограничением глубины до 10 уровней.

👉 Live Recomposition Heatmap
Прямо в редакторе, над каждой composable-функцией, в реальном времени отображается количество рекомпозиций с подключённого устройства через ADB. Цветовая индикация:
🟢 < 10 — всё ок
🟡 10–50 — стоит присмотреться
🔴 50+ — проблема

Данные читаются из TraceRecomposition событий logcat. Поддерживается несколько устройств одновременно.

#Compose #Performance
Please open Telegram to view this post
VIEW IN TELEGRAM
153👍17👎4🔥3🤔3
Forwarded from Aurora Developers (Vitaliy Zarubin)
🔥 Compose Multiplatform доступен на ОС Аврора!

Отличные новости для Kotlin-сообщества и всех, кто интересуется российской мобильной разработкой. Теперь вы можете создавать приложения для ОС Аврора, используя современный и привычный инструментарий — Kotlin и Compose Multiplatform.

Что опубликовано:
В репозитории на mos.hub выложены компоненты, необходимые для разработки:

- Проекты Compose Multiplatform для сборки.
- Готовые артефакты в репозитории Aurora Maven.
- Плагин для Gradle, упрощающий сборку приложений.

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

👉 Документация

Хотите помочь проекту стать лучше?
Присоединяйтесь к разработке на mos.hub! Мы открыты к вашему коду и экспертизе. Только #ВМЕСТЕ мы сможем сделать инструмент максимально полезным для всего сообщества.

👉 Репозиторий
👎49🔥32🤔8👍43
Media is too big
VIEW IN TELEGRAM
#see Compose Multiplatform на ОС Аврора

Демонстрация сборки приложения Compose Multiplatform на эмулятор ОС Аврора.

▶️ Rutube
Please open Telegram to view this post
VIEW IN TELEGRAM
👎44👍27🤔3
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
👍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
🔥46👏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
🔥33👎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👍3
🐱 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
👍10👎6