Android & Coffee с Васей
182 subscribers
74 photos
5 videos
50 links
Канал для разговоров за кофе о разработке. В основном Android, но вообще как получится
Download Telegram
Jetpack Compose: весь changelog в одном месте

Новый Compose выходит 1-2 раза в месяц. И каждый раз обновление превращается в квест. Мне. Надоело.

Приходится вручную:

1. Идти на BOM to library version mapping, смотреть какие библиотеки поменялись
2. Для КАЖДОЙ из них открывать changelog на отдельной странице — и ещё помнить, какая версия была до этого
3. Отдельно искать What's new in the Jetpack Compose — полезно, но не очевидно где найти

Обновляешь сразу на две версии — всё это x2.

👉 Короче, собрал сайт со всеми изменениями. Лично кайфанул от того, что похоже на developer.android.com, при этом всё видно сразу

Плюс сделал AI-friendly: есть llms.txt, чтобы агентам было проще разбирать изменения и помогать с апгрейдами

Пригодится всем, кто регулярно обновляет Compose. Ну и агентам скармливать удобно — пусть сами разбираются с апгрейдами.

⭐️ Если пригодится — звёздочка на GitHub и репост приветствуются. Чем больше людей узнает, тем быстрее убедим Google выпустить свою версию.
👏9
Android & Coffee с Васей
Jazari One — часть 2 И вот момент настал. Весь долг пришлось отдавать, потому что иначе поддерживать два приложения очень дорого. Кто занимается проектированием, знает, что идеальной архитектуры не существует. Каждое решение — это компромисс между требованиями…
Jazari — UK-банкинг — запущено, стабильно работает у пользователей уже почти год. Мы решили, что хотим сделать еще приложение на стейблкоины — другая аудитория, другая регуляция, другой продукт.

Как оно будет выглядеть — не знает никто. Экспертизы по стейблкоинам в команде почти нет. Всё обсуждается по ходу и может поменяться к следующему утру. Спросить некого: мы сами и рождаем это приложение прямо сейчас.

О завершённых проектах обычно рассказывают итог: «сделали вот так, всё работает». Трейдофы — что отбросили, почему, что пошло не туда — остаются за кадром.

Я хочу рассказать именно это. В основном про Android приложение. И ещё — где во всём этом помог AI, а где нет.

Первый пост — завтра

Кажется, получится долгий рассказ, поэтому буду помечать посты:

серия → «Два приложения — один логин»

#android #kotlin #androiddev #архитектура
4👍1
Android & Coffee с Васей pinned «Jazari — UK-банкинг — запущено, стабильно работает у пользователей уже почти год. Мы решили, что хотим сделать еще приложение на стейблкоины — другая аудитория, другая регуляция, другой продукт. Как оно будет выглядеть — не знает никто. Экспертизы по стейблкоинам…»
🪨 Монолит

Первый трейдоф появился задолго до того, как появился второй проект.

Когда делали Jazari Money, был вопрос — модуляризировать или нет. Модуляризация требует времени и на подготовку, и на поддержку. Поэтому все фичи делались в одном модуле. В условиях стартапа выбор был сделан в пользу скорости.

Так вырос один :app модуль — 600+ Kotlin-файлов, куча ресурсов, различные тесты. Логин и регистрация жили прямо внутри: app/feature/login/, app/feature/registration/. Минимум отдельных модулей.

Итак, дано: работающая куча кода 💁🏻

Задача: нужно сделать два разных (но частично похожих) приложения — и не сломать существующее 📝

Обычно к такой задаче не знаешь как подступиться. Было именно такое ощущение. И одновременно — воодушевление: казалось, что жизнь готовила меня именно к таким задачам.

Подход к проекту был последовательным: сначала подготовить почву, потом строить. Больше 100 коммитов — модуляризация монолита. В результате :app стал :app:jazari-money, логин и регистрация переехали в отдельные feature-модули, UIKit и утилиты — в core. И только поверх этой готовой структуры, тремя последними коммитами, появился :app:jazari-global.

Но прежде чем двигаться, пришлось принять несколько решений. О них — в следующих постах.

→ серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура
2👍1
Почему мы не стали делить feature на api и impl

Часть фич нужно переиспользовать, а значит пришло время разделить монолит. Как организовать feature-модули? Есть два основных подхода.

1️⃣ Подход 1: api + impl. Каждая фича делится на два модуля. Фичи могут зависеть друг от друга через :api.

Этот подход активно используется в крупных компаниях. Например, именно так мы делали в Кинопоиске. Вот еще пара статей, где компании рассказывают о таком подходе:
Альфа-раз, Альфа-два
Касперский раз, Касперский два

Плюсы:
• легче изучать код — очевидно откуда куда навигируемся
• наличие api модуля позволяет четко разграничить ответственность между командами — поэтому подход используют во многих крупных компаниях

Минусы:
• фичи связаны между собой, пусть даже через контракт
• модулей вдвое больше — увеличивается время сборки, появляется бойлерплейт

2️⃣ Подход 2: один модуль на фичу. Фичи не могут зависеть друг от друга.

Плюсы:
• чёткая зона ответственности фичи
• фичи полностью независимы

Минусы:
• интеграцию нужно явно прописывать в app модуле. Это чуть усложняет изучение поведения фичи в готовом приложении.

Такой подход описывает в своей документации Google

*️⃣ Есть и другие подходы (например, api + internal в Яндекс картах)

👉 api + impl — монументальная структура: много модулей, фичи знают друг о друге. Она хороша для устоявшегося приложения с большой командой, но для компактного стартапа — избыточна. Выбрали подход 2️⃣, один модуль на фичу

И ещё один аргумент за подход 2: один фича-модуль не отрезает путь к api + impl в будущем. Обратный переход — сложнее, потому что больше кросс-зависимостей. Поэтому пока что — YAGNI

Про то, как интеграция фич выглядит на практике — дальше.

серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура
2👍1
AI не создал новые проблемы в разработке. Он сделал видимыми старые.

Конец недели — пора расслабиться. Следующий пост про трейдофы при модуляризации будет на следующей неделе, а пока поделюсь очень интересным, на мой взгляд, видео.

Adam Bender (Google) в докладе на I/O 2026 вводит понятие «software ecology» —
программный продукт неотделим от организации, которая его строит.
Закон Конвея в действии: не только архитектура системы,
но и код-ревью, постмортемы, деплой — всё отражает ценности компании.

AI убрал узкое место там, где его почти не было — в написании кода.

Раньше инженер часами писал код. Теперь за секунды. Но дальше — стена:
— Ревью кода, который никто не писал руками
— Интеграционные тесты, которых нет или они недостаточно хорошие
— «Conjunction of Booleans»: чтобы выпустить билд, все тесты должны быть зелёными. Но что делать, если тестов миллион? Всё-или-ничего стратегия больше не работает

Узкое место сместилось. Теперь оно — верификация и деплой.

Bender называет это «capacity crunch за пределами написания кода».
Производить код легко. Убедиться, что он работает — нет.

Это меняет не инструменты. Это меняет то, во что нужно инвестировать команде.

Еще интересно то, что Адам НЕ сказал. В основном он упоминает технические вещи: деплой, пропускная способность сети, сколько коммитов в минуту может выдержать VCS. Это точно будет узким местом, но я верю, что AI поможет справится с этим

Гораздо менее понятно — что делать с узким местом в виде людей, которые должны поставлять AI прожаренные фичи с более-менее понятными требованиями. Кажется, что AI должен будет стать полноправным членом команды на всех этапах процесса: участвовать во встречах, анализировать данные рынка, составлять бэклог и приоритизировать его вместе с командой. Вот это действительно дало бы огромный буст

Хотели бы видеть AI членом своей команды?

#AI #googleio
Convention Plugins

Итак, будет больше одного фича модуля, а значит собираться они будут похожим образом. Нужно избежать бойлерплейта. И вот как сейчас выглядит build.gradle.kts любого feature-модуля в проекте:


plugins {
alias(libs.plugins.jazari.feature)
}

android {
namespace = "money.jazari.feature.login"
}


В отдельных модулях бывают дополнительные зависимости. Но всё же — несколько строк, вместо ~50 с compileSdk, версией Java, Compose BOM, всеми зависимостями Hilt, KSP, desugaring и прочим. Это convention plugins.

Бывают в виде kts скриптов или в виде kotlin/java файлов. Без них — копипаст в каждом модуле. Один раз забыл обновить compileSdk в одном месте — и привет.

Получилась иерархия, как на скриншоте. FeaturePlugin — это буквально:


class FeaturePlugin : BaseProjectPlugin() {
override fun apply(target: Project) {
AndroidLibraryPlugin().apply(target)
HiltPlugin().apply(target)
ComposePlugin().apply(target)
}
}


Все модули получают нужную конфигурацию за одну строку.

Вот немного полезных материалов по теме, от Т-Банка:
Статья раз, Статья два

Кстати, написание самих плагинов — хороший пример задачи для AI: есть образец в виде build.gradle.kts, нужно отрефакторить, чтобы собиралось также. Особенно помогает, когда есть gradle апи, перенос которых в плагин не самый очевидный. Тут AI справился без вопросов и без помощи извне

серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура
👍4
Нужно ли выносить внешние зависимости в отдельные модули?

Многие архитектурные паттерны рекомендуют это делать. На практике — зависит.

Если отгораживаться модулями от всех зависимостей, появляются дополнительные абстракции, маппинги и модели. Архитура усложняется, поэтому обычно для этого нужна веская причина.

Например, Android SDK — тоже внешняя зависимость для нашего кода. Но мы же не изолируем всё подряд без необходимости.

У нас таких случаев нашлось два: пуши и саппорт. Оба — внешние SDK, которые могут меняться или подключаться по-разному в разных приложениях.

С саппортом получился показательный кейс. Параллельно с модуляризацией мы переезжали с Intercom на HelpCrunch. Каждый SDK был вынесен в отдельный модуль, поэтому оба работали одновременно, а переключение было возможно прямо на проде — без страха что что-то сломается.

Когда миграция завершилась, удаление Intercom выглядело как удаление одной строки из build.gradle.kts.

Все остальные внешние зависимости оставили как есть. Изолировать всё подряд ради соблюдения паттернов кажется избыточным.

серия «Два приложения — один логин»
#android #kotlin #androiddev #архитектура
👍2
Модуляризация закончена.

Вынесли в отдельные модули ui kit, data, models, domain и другие общие части. В итоге получилось: 15 core-модулей, 6 feature-модулей и domain. Часть фич осталась в монолите — их можно выделить позже, если появится необходимость

Модуляризация заняла 100+ коммитов. Отдельная часть работы — исправление нарушений слоёв, которые накопились в монолите

Какие части помог сделать AI?

🤖 AI для выбора архитектуры

AI может проанализировать проект, учесть предпочтения по архитектуре, найти узкие места и предложить варианты. Если оставить его наедине с кодом — думаю, он даже успешно всё отрефакторит

Но есть важный нюанс: ответственность за архитектуру несём мы, а не AI

Поэтому мы сделали так:
• сами спроектировали будущую архитектуру, понимая её плюсы и минусы
• обсудили решение с AI, попросили найти риски и наложить его на проект
• доработали архитектуру после обсуждения

Цена ошибки тут слишком высокая, чтобы делегировать решение целиком

🤖 AI для рефакторинга

А вот здесь AI оказался очень полезен: составить план, подсветить проблемные зависимости, двигать файлы, помогать с переносом кода. Это реально экономит много времени

👉 Главный вывод: полноценная модуляризация одного приложения — часто преждевременная оптимизация

Но структуру будущих модулей стоит закладывать сразу, даже внутри монолита:

app/feature/login/
app/feature/registration/
app/domain/

Тогда при росте проекта границы уже понятны: переносишь папку — получаешь модуль. А нарушения этих границ потом стоят десятков лишних коммитов (но AI помогает)

Модуляризация выполнена. Общие feature-модули готовы. Новое приложение почти готово?

Ага, конечно 🙂

серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🧹 Небольшая история из-под капота compose-bom.com

Поймал любопытный кейс. Google выпустил Compose BOM 2026.05.01, но потом тихо убрал его из официального BOM-маппинга. Причина — рассогласование версий: runtime в этом BOM — 1.10.6, тогда как всё остальное уже 1.11.x.

Хитрость в том, что артефакты в Maven неизменны — поэтому 2026.05.01 так и лежит там навсегда, хотя Google уже «отозвал» его и заменил исправленным 2026.06.00 (там runtime подняли до 1.11.2, чтобы всё совпадало).

Получался релиз-призрак: в Maven есть, в официальном маппинге — нет 🤷
Теперь коллектор такие отозванные версии аккуратно пропускает, чтобы в changelog попадало только то, что Google реально поддерживает.

=====

👉 Напоминаю, что это бесплатный инструмент: показывает дифф между двумя версиями Compose BOM и собирает release notes библиотек в одном месте. Адаптирован для AI агентов с помощью llms.txt

Пользуйтесь, делитесь с коллегами — буду рад, если пригодится 🙌

🔗 compose-bom.com

#JetpackCompose #AndroidDev #Kotlin #ComposeBOM #Android
👍4
This media is not supported in your browser
VIEW IN TELEGRAM
Модули готовы. Следующая цель — запустить второе приложение.

Создали новый app-модуль. Нужно добавлять зависимости. DI-граф для нового приложения не возникает из воздуха — кто-то должен всё забиндить. И тут мы поступили максимально по-варварски: взяли AppModule.kt из Jazari Money на 400 строк, скопировали в Jazari Global и начали удалять лишнее.

В итоге — 301 строка. Примерно 60% одинаковые.

И AppModule — только самый наглядный пример. Копировалось всё, что живёт на уровне `app`-модуля: конфигурация, инициализаторы, навигация. Сценарий везде одинаковый: копируешь, удаляешь лишнее, добавляешь чего не хватило

Трейдоф здесь честный.

С одной стороны — дублирование. Добавил что-то в одном приложении — не забудь обновить второе.

С другой — абстракции не бесплатны. Разница между приложениями реальная, и любая «общая база» быстро начинает обрастать исключениями.

Технический долг? И да, и нет.

Зависит от того, сколько проблем это реально создаст. Иногда copy-paste — лучший архитектурный выбор. Главное — понимать цену переделки, если она понадобится.

Осталось склеить фичи между собой.

Один модуль = одна фича. Значит, фичи не знают друг про друга. Навигация живёт в `app`-модуле.

Каждая фича предоставляет extension на NavGraphBuilder:


fun NavGraphBuilder.createLoginScreen(
navController: NavController,
onTermsClick: () -> Unit = {},
onPrivacyPolicyClick: () -> Unit = {},
)


feature:login не знает, что делать после клика на T&C. Это просто callback, который реализует `app`-модуль. Jazari Money передаёт свою навигацию. Jazari One — свою.

Одна и та же фича, но возможно разное поведение.

В `app`-модуле приходится явно прописывать все переходы. Плюс это или минус — решайте сами 😉

И вот оно — приложение запустилось.

На видео самая первая версия: без анимаций, с кривой тёмной темой, временными заглушками вместо онбординга и главного экрана. Почти ничего нет из того, что запланировано — но сколько радости, потому что ОНО РАБОТАЕТ!!

серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура
4
Новость: SpaceX покупает Cursor

— Как рассчитали траекторию полёта?
— На вайбе
😁6
Android & Coffee с Васей
🧹 Небольшая история из-под капота compose-bom.com Поймал любопытный кейс. Google выпустил Compose BOM 2026.05.01, но потом тихо убрал его из официального BOM-маппинга. Причина — рассогласование версий: runtime в этом BOM — 1.10.6, тогда как всё остальное…
Google удалил Compose BOM 2026.06.00 и переопубликовал 2026.05.01, с поправленной runtime зависимостью. Теперь всё чинно.

Если мигрируете — изменения можно посмотреть на https://compose-bom.com/ (или натравите агента — сайт llm-friendly)
Blueprint Compose Preview

Библиотека от Gus Ward, позволяющая показать Blueprint экрана на Compose.

Достаточно обернуть экран в composable BlueprintPreview и добавить идентификаторы к некоторым элементам. Удобно для проверки вёрстки и поиска проблем с отступами без запуска приложения.

Есть режим showInternalItems, когда показываются все возможные элементы. Для превью экрана это чересчур (3й скрин), но может пригодиться для превью отдельных компонентов дизайн системы

И удобно, когда дизайнер говорит "вроде тут не 32 пикселя, а 33"

⭐️ Github: https://github.com/GusWard/Blueprint-Compose-Preview

#android #androiddev #jetpackcompose #compose #mobiledev #kotlin #ui #uidesign #androidlibrary
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
Один логин?

Приложение собрано. Открываем первую же фичу — логин. Первый экран: ввод номера телефона.

В Jazari Money страна одна — UK. Флаг захардкожен, поменять нельзя. В Jazari One пользователь может сменить код страны прямо в интерфейсе, аудитория глобальная.

Уже здесь — дилемма. Экран ввода телефона в двух приложениях отличается, но не полностью. Различия постарались убрать по максимуму, но от них никуда не деться. Варианта два:

— два отдельных экрана, каждый со своим поведением
— один экран с if'ами

Первый вариант чище концептуально: никаких if-ов, каждое приложение получает свой экран. Но слишком большая часть — вёрстка, валидация телефона, отправка OTP — была бы продублирована.

Вот тут дублировать уже не хочется, потому что расходы на поддержку кажутся большей проблемой, чем при поддержке DI модулей.

Итак, решение — один модуль, одна вьюмодель, одна вьюха, но есть отличия в вёрстке и логике. Как это выразить?

→ серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура
👍4🔥1
Один логин, разное поведение

В Jazari Money при логине нельзя поменять код страны, в Jazari One — можно. Как это сделать?

Вот несколько способов, как сделать фичи настраиваемыми под разные бренды

1️⃣ — общий интерфейс AppVariant. Каждое приложение биндит свою реализацию через DI, а feature-модуль просто спрашивает у него что нужно.


interface AppVariant {
val brand: Brand
val supportEmail: String
// ...
}


brand — универсальное поле, которое позволяет писать if'ы. Более правильным решением было бы завести отдельные поля:


interface AppVariant {
val canChangeCountry: Boolean
val defaultCountry: Country?
val supportEmail: String
// ...
}


Но когда отличия накапливаются, на каждое заводить по флагу или другому полю — представьте сколько их будет.

При этом в Jazari Money никогда не предвидится выбора другой страны, а дефолтная страна всегда будет UK. То есть абстрагировать здесь сомнительно, хоть и можно. Проверка бренда, пусть и не такая чистая, зато проще.

Получается примерно так:


phoneState = when (appVariant.brand) {
// всегда UK
Brand.Money -> PhoneCountry(flag = flag_uk, code = "+44", iso = "GB", canChangeCountry = false)

// выбранная страна состояния
Brand.Global -> loginStorage.savedCountry()
}


Минус: фича знает про то, что бренды есть.

Плюс: фича требует минимум настройки и уже может работать с другим брендом.

2️⃣ Поле supportEmail в AppVariant — другое дело. Оно используется в нескольких местах, присутствует в обоих приложениях и имеет разные значения. Здесь уже есть смысл сделать поле вместо if'ов по всем модулям.

3️⃣ Еще один вариант — конфигурация конкретной фичи вместо общей конфигурации приложения:


data class EnterPhoneConfig(
val canChangeCountry: Boolean,
val defaultCountry: Country?,
)


Каждая фича — свой конфиг. AppVariant не разрастается хаотично.

Самая чистая абстракция. Но и кода вокруг неё больше.

Пока у нас не white-label и мы не поставляем SDK, большого смысла в таком решении для фич я не вижу. Но это хорошо работает для инфраструктурных модулей с понятной настройкой — например, параметров пушей или Network слоя.

---

В проекте в итоге используются все три подхода — в разных местах и по разным причинам.

Кажется, это и есть ответ: универсального решения нет.

С поведением разобрались. Что с UI?

→ серия «Два приложения — один логин»

#android #kotlin #androiddev #архитектура
🔥3
This media is not supported in your browser
VIEW IN TELEGRAM
🤯 Новый One UI от Samsung

За такое нужно увольнять без выходного пособия. И дизайнера, и разработчика.

У меня уже дёргается глаз от этой анимации.

Что думаете?

#android #ux #samsung #oneui
💯2