Визуальный гайд по структуре мультиплатформенных проектов
Хотите быстро объяснить коллеге, как устроить архитектуру с KMP/CMP? Это лучший вариант!
👉 Compose MP c разделением по слоям
👉 Kotlin MP с общим presentation слоем. Странный вариант, есть идеи когда это выигрывает?
👉 Kotlin MP c общим data слоем.
👉 Kotlin MP, как общий модуль для нативных приложений. На мой взгляд это самый лучший вариант, но продать эту идею iOS команде еще не получалось
👉 Стандартный вид CMP предлагаемый из шаблонов
Больше возможных архитектурных шаблонов с KMP/CMP: https://github.com/TheSetox/kmp-sample-diagrams
Хотите быстро объяснить коллеге, как устроить архитектуру с KMP/CMP? Это лучший вариант!
👉 Compose MP c разделением по слоям
👉 Kotlin MP с общим presentation слоем. Странный вариант, есть идеи когда это выигрывает?
👉 Kotlin MP c общим data слоем.
👉 Kotlin MP, как общий модуль для нативных приложений. На мой взгляд это самый лучший вариант, но продать эту идею iOS команде еще не получалось
👉 Стандартный вид CMP предлагаемый из шаблонов
Больше возможных архитектурных шаблонов с KMP/CMP: https://github.com/TheSetox/kmp-sample-diagrams
Устали писать обслуживающий код? Автоматизируем все с помощью KSP
В кратце, автор генерирует вспомогательные класса для навигации, которая с последними обновлениями стала слишком тяжелой. Все работает благодаря самописным аннотациям и генератору. Используете ли вы нечто похожее?
В кратце, автор генерирует вспомогательные класса для навигации, которая с последними обновлениями стала слишком тяжелой. Все работает благодаря самописным аннотациям и генератору. Используете ли вы нечто похожее?
Medium
Kotlin KSP — how to automate everything in the world
Not long ago, Google rolled out a super cool update for automating boilerplate generation. Of course, I’m talking about Kotlin Symbol…
Красивое решение для LazyList с канала мобильное чтиво. Многие в процессе миграции с xml на compose, так что берите элегантный хак на заметку!
Telegram
Мобильное Чтиво
Очень серьезный канал про мобильную разработку. Веду канал я — @maxkachinkin
Forwarded from Мобильное Чтиво (Maxim Kachinkin)
This media is not supported in your browser
VIEW IN TELEGRAM
🎨 Главное — чтобы было красиво!
Тесты, шместы, архитектура — это всё прекрасно. Но в итоге главное — чтобы было красиво! Я вот вспомнил одну нашу фичу, где надо было сделать кастомный контрол типа табов, которые плавно анимировались, центрировались на выбранном, а потом обратно схлопывались. Всё на Compose, конечно. 💻
И что вы думаете? Контентные паддинги в LazyRow не помогли, игры с отступами тоже. Даже использование horizontalScroll не дало результата. Пришлось думать дальше. 🤔
А как в итоге сделали? Ну, это можно назвать костылём (или нормальным решением, если вам так больше нравится). Добавили "фейковые" элементы в начале и в конце списка и анимировали их размер. 🙃
Используем LazyRow и делаем первый и последний item просто прозрачные Spacer, чтобы создать видимость отступов. Плавно и красиво анимируем их ширину, и всё! 💫 На самом деле не совсем всё: это тянет за собой много всего, чтобы учитывать эти элементы по-особенному (чтобы не кликались, не анимировались, не участвовали в выборе и т.д.).
Как заметили в комментариях, это создает дополнительные рекомпозиции 🫣, что не может не радовать. Такой трейдофф решили взять. Но в итоге всё выглядит плавно, аккуратно, ну и просто красиво! 🌟
В комментах я добавлю скриншот кода и видосик — там видно, как это анимируется и центрируется. 🎥
А у вас какие были проблемы из-за красоты? Поделитесь! 😎
#android #compose #ui #lazyrow
Тесты, шместы, архитектура — это всё прекрасно. Но в итоге главное — чтобы было красиво! Я вот вспомнил одну нашу фичу, где надо было сделать кастомный контрол типа табов, которые плавно анимировались, центрировались на выбранном, а потом обратно схлопывались. Всё на Compose, конечно. 💻
И что вы думаете? Контентные паддинги в LazyRow не помогли, игры с отступами тоже. Даже использование horizontalScroll не дало результата. Пришлось думать дальше. 🤔
А как в итоге сделали? Ну, это можно назвать костылём (или нормальным решением, если вам так больше нравится). Добавили "фейковые" элементы в начале и в конце списка и анимировали их размер. 🙃
Используем LazyRow и делаем первый и последний item просто прозрачные Spacer, чтобы создать видимость отступов. Плавно и красиво анимируем их ширину, и всё! 💫 На самом деле не совсем всё: это тянет за собой много всего, чтобы учитывать эти элементы по-особенному (чтобы не кликались, не анимировались, не участвовали в выборе и т.д.).
Как заметили в комментариях, это создает дополнительные рекомпозиции 🫣, что не может не радовать. Такой трейдофф решили взять. Но в итоге всё выглядит плавно, аккуратно, ну и просто красиво! 🌟
В комментах я добавлю скриншот кода и видосик — там видно, как это анимируется и центрируется. 🎥
А у вас какие были проблемы из-за красоты? Поделитесь! 😎
#android #compose #ui #lazyrow
В понедельник утром оптимизируем загрузку изображений в compose и kotlin MP
Сохраню вам время. Общий посыл - используйте landscapist (2100+⭐️ ) Шустрый плагин, совместимый с coil, fresco и glide.
Работает как с последним coil3, превьюхами Android Studio и wasm. Используем?
Сохраню вам время. Общий посыл - используйте landscapist (2100+
Работает как с последним coil3, превьюхами Android Studio и wasm. Используем?
Please open Telegram to view this post
VIEW IN TELEGRAM
А еще compose 1.7.0-rc01 с большим количеством оптимизаций и исправлений для Compose Multiplatform проектов. iOS версия нашего проекта стала значительно бодрее. Хотя в альфе, версии к версии, не все работало хорошо.
Please open Telegram to view this post
VIEW IN TELEGRAM
Как Uber мигрирует нетворк слой на gRPC
Знакомая боль всех мобильный разработчиков с миграцией чего либо на слое данных. Как решает проблему Uber:
👉 Protocol Buffers вместо JSON, HTTP/2 без откатов к HTTP/1.1
👉 Использование gRPC, на который мигрировали постепенно.
С помощью shadow calls проверяли скорость работы и PoC. Это позволило провести небольшое исследование на проде и увидеть результаты без больших затрат.
Поэтапно переводили API, по принципу A/B тестирования и откату к старой REST версии в случае возникновения проблем. Круто же!
👉 Эксперимент привел к снижению нагрузки на 45% и уменьшению задержки запросов на 27%
У компаний сейчас большой запрос на оптимизацию затрат, и это одно из технических решений которые вы можете предложить! Чуть больше по поводу gRPC и Android разработке: https://developer.android.com/guide/topics/connectivity/grpc
Знакомая боль всех мобильный разработчиков с миграцией чего либо на слое данных. Как решает проблему Uber:
👉 Protocol Buffers вместо JSON, HTTP/2 без откатов к HTTP/1.1
👉 Использование gRPC, на который мигрировали постепенно.
С помощью shadow calls проверяли скорость работы и PoC. Это позволило провести небольшое исследование на проде и увидеть результаты без больших затрат.
Поэтапно переводили API, по принципу A/B тестирования и откату к старой REST версии в случае возникновения проблем. Круто же!
👉 Эксперимент привел к снижению нагрузки на 45% и уменьшению задержки запросов на 27%
У компаний сейчас большой запрос на оптимизацию затрат, и это одно из технических решений которые вы можете предложить! Чуть больше по поводу gRPC и Android разработке: https://developer.android.com/guide/topics/connectivity/grpc
Разбираемся с FileProvider
Банальная вещь, но сэкномит вам время
👉 Базово в манифесте определяем провайдер. Решаем нужно ли давать разрешениями на пользование нашими данными другим приложениям (
👉 Определяем
👉 Не забываем про permissions. Можно обойтись без прямого доступа к файловой системе:
Пример использования для того чтобы пошарить картинку из кэша в email без выгрузки bitmap:
Банальная вещь, но сэкномит вам время
👉 Базово в манифесте определяем провайдер. Решаем нужно ли давать разрешениями на пользование нашими данными другим приложениям (
grantUriPermissions
, exported
)👉 Определяем
<paths>
:<files-path>
Для файлов внутреннего хранилища ([pm]/files/)<cache-path>
Для кэша ([pm]/cache/)<external-path>
Внешнее хранилище (/storage/emulated/0/). Учитывайте, что доступ сюда будет иметь любое приложение и без вашего ведома<external-files-path>
Внешнее приватное хранилище (/storage/emulated/0/Android/data/[package_name]/files/)<external-cache-path>
Внешний приватный кэш (/storage/emulated/0/Android/data/[package_name]/cache/)<root-path>
Доступ напрямую из корня. Тут надо осторожно, у вашего приложения может не быть прав на запись 👉 Не забываем про permissions. Можно обойтись без прямого доступа к файловой системе:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Пример использования для того чтобы пошарить картинку из кэша в email без выгрузки bitmap:
val imagePath = File(context.getCacheDir(), "images")
val newFile = File(imagePath, "image.png")
val contentUri = FileProvider.getUriForFile(context, "com.example.myapp.fileprovider", newFile)
if (contentUri != null) {
val shareIntent = Intent()
shareIntent.setAction(Intent.ACTION_SEND)
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
shareIntent.setDataAndType(contentUri, context.getContentResolver().getType(contentUri))
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri)
startActivity(Intent.createChooser(shareIntent, "Choose an app"))
}
This media is not supported in your browser
VIEW IN TELEGRAM
Ink API, поддержка рисовалок для Android приложений
Корректная поддержка стилусов. Работает с API 21 (Android 5.0)
Возможности включают в себя кисти, обьекты, обработка отрисованной линии, чтобы она казалась более плавной. Не хватало этого во многих приложениях, особенно если ты пользуешься стилусом. Теперь поддержка стилуса является частью гайдлайнов гугла по созданию приложений. Глянуть и вдохновиться можно тут: https://developer.android.com/adaptive-apps
Корректная поддержка стилусов. Работает с API 21 (Android 5.0)
Возможности включают в себя кисти, обьекты, обработка отрисованной линии, чтобы она казалась более плавной. Не хватало этого во многих приложениях, особенно если ты пользуешься стилусом. Теперь поддержка стилуса является частью гайдлайнов гугла по созданию приложений. Глянуть и вдохновиться можно тут: https://developer.android.com/adaptive-apps
Абстракция для работы со строками
Возникали вопросы как работать со строками в Compose на UI и на data слое чтоб это выглядело удобно? А еще чтобы строки с разных источников были закрыты общей абстракцией и не нужно было разделять строки от пользователя,
Автор предлагает воспользоваться
Пример использования:
Из минусов - конечно сложновато это адаптировать к CMP, да и протекание логики в UI может не соответствовать вашей архитектуре.
Возникали вопросы как работать со строками в Compose на UI и на data слое чтоб это выглядело удобно? А еще чтобы строки с разных источников были закрыты общей абстракцией и не нужно было разделять строки от пользователя,
string.xml
и пришедшие с бекендаАвтор предлагает воспользоваться
sealed interface
такого вида:sealed interface StringHolder {
// These are the 2 essential Holders
data class Value(val value: String) : StringHolder
data class Resource(@StringRes val resId: Int) : StringHolder
// These are additional and will come in handy!
class ParametrizedResource(@StringRes val resId: Int, vararg val formatArgs: Any) : StringHolder
class Plural(@PluralsRes val resId: Int, val count: Int, vararg val formatArgs: Any) : StringHolder
}
Пример использования:
@Composable
fun ExampleText(stringHolder: StringHolder) {
Text(text = stringHolder.value)
}
class MessageDataSource(private val context: Context, private val api: Api) {
suspend fun sendMessage(message: StringHolder) {
api.sendMessage(message.fromContext(context))
}
}
Из минусов - конечно сложновато это адаптировать к CMP, да и протекание логики в UI может не соответствовать вашей архитектуре.
Android Good Reads
Как Uber мигрирует нетворк слой на gRPC Знакомая боль всех мобильный разработчиков с миграцией чего либо на слое данных. Как решает проблему Uber: 👉 Protocol Buffers вместо JSON, HTTP/2 без откатов к HTTP/1.1 👉 Использование gRPC, на который мигрировали…
А вот и демонстрация Kotlin RPC. На примере создания фуллстак приложения с KMM, Koin, Wasm
Самый приятный момент это в том что клиент и сервер шарят интерфейсы, модели и статусы. В самой статье много кода, так рекомендую взглянуть.
Из болезненного, со слов автора, это WASM, который еще в альфе, и высокий порог входа. Нужно быть чуть знакомым с kmm концепцией, чтобы разобраться в этом.
Самый приятный момент это в том что клиент и сервер шарят интерфейсы, модели и статусы. В самой статье много кода, так рекомендую взглянуть.
Из болезненного, со слов автора, это WASM, который еще в альфе, и высокий порог входа. Нужно быть чуть знакомым с kmm концепцией, чтобы разобраться в этом.
👉 Ktor 3.0 Миграция на
kotlinx-io
, немного улучшений перфоманса, поддержка WASM по всем направлениям. Миграция не сложная👉 Фиксы и немного стабильности в Kotlin 2.0.21. Теперь поддерживается Xcode 16, а для тех кто любит быть на острие технологий Kotlin 2.1.0-Beta2. Страничка со всеми изменениями тут, но в кратце:
⚡️Условия в when
⚡️break и continue в лямбдах
⚡️улучшения в составлении строковых переменных
⚡️Улучшение k2 kapt (все еще в альфе)
⚡️AGP минимальная теперь 7.3.1, а Gradle 7.6.1
⚡️Большое количество улучшений для WASM и компилятора
👉 Compose Multiplatform 1.7.0. Тут нас ждёт compile-time safety для навграфа, огромные улучшения по перформансу для iOS. Мы с командой уже заценили, хоть и не все было гладко на предрелизных версиях. Material3 библиотеки постепенно мигрируют в общую кодовую базу. Уже привычный Shared element transitions тоже тут!
Please open Telegram to view this post
VIEW IN TELEGRAM
Все чаще мне попадаются статьи с отказом от DI фреймворков. Пока что не видел какого-либо жизнеспособного решения в продакшене без DI.
Автор одной из минималистичных DI библиотек - Magnet тоже предлагает отказаться от DI и использовать явные зависимости. И почему же?
👉 Это сложно
👉 Время сборки сильно вырастает
👉 Время холодного старта тоже увеличивается
👉 Часто обьекты графа остаются в памяти дольше чем нужно. Да, большинство команд просто плодит синглтоны в том или ином виде и инжектит их. Увы, это и правда плохой подход
Взамен предлагается подход, кратно увеличивающий количество обслуживающего кода, однако использующий ручное и явное внедрение зависимостей:
Больше примеров внутри статьи
Таким образом, мы получим достаточно прозрачную систему внедрения зависимостей в проекте, где нет никакой магии и у нас в руках полный контроль над всеми обьектами, однако масштабируемость этого подхода вызывает вопросы. Что вы думаете про тренд отказа от DI?
Автор одной из минималистичных DI библиотек - Magnet тоже предлагает отказаться от DI и использовать явные зависимости. И почему же?
👉 Это сложно
👉 Время сборки сильно вырастает
👉 Время холодного старта тоже увеличивается
👉 Часто обьекты графа остаются в памяти дольше чем нужно. Да, большинство команд просто плодит синглтоны в том или ином виде и инжектит их. Увы, это и правда плохой подход
Взамен предлагается подход, кратно увеличивающий количество обслуживающего кода, однако использующий ручное и явное внедрение зависимостей:
interface NetworkModule {
val httpClient: HttpClient
class Default : NetworkModule {
override val httpClient: HttpClient by lazy ...
}
}
interface DatabaseModule {
val database: Database
class Default : DatabaseModule {
override val database: Database by lazy ...
}
}
interface RssReaderModule {
fun fetchRss(): FetchRss
class Default(
private val networkModule: NetworkModule,
private val databaseModule: DatabaseModule,
) : UserModule {
override fun fetchRss(): FetchRss ...
}
}
Больше примеров внутри статьи
Таким образом, мы получим достаточно прозрачную систему внедрения зависимостей в проекте, где нет никакой магии и у нас в руках полный контроль над всеми обьектами, однако масштабируемость этого подхода вызывает вопросы. Что вы думаете про тренд отказа от DI?
Преиспользуем стили в Jetpack Compose
Чтобы сотню раз не проставлять паддинги и обводки в дизайн/системе, объединяем в простой универсальный
И несколько предупреждений:
👉 Не нужно паковать весь интерфейс внутрь одной функции, сохраняйте атомарность и переиспользуемость таких модификаторов
👉 Не делайте слишком глубокую вложенность из таких модификаторов. Это намного сложнее исправлять
Чтобы сотню раз не проставлять паддинги и обводки в дизайн/системе, объединяем в простой универсальный
Modifier
:
fun Modifier.paddedBorder(): Modifier {
return this
.border(1.dp, Color.Black)
.padding(8.dp)
}
fun Modifier.cardStyle(): Modifier {
val shape = RoundedCornerShape(4.dp)
return this
.shadow(4.dp, shape)
.background(Color.White)
}
И несколько предупреждений:
👉 Не нужно паковать весь интерфейс внутрь одной функции, сохраняйте атомарность и переиспользуемость таких модификаторов
👉 Не делайте слишком глубокую вложенность из таких модификаторов. Это намного сложнее исправлять
По мере роста проекта возникает всё больше и больше проблем с производительностью UI. Что-то мы отлавливаем автоматически на пулреквестах, что то по репортам пользователей. О том как от релиза к релизу найти снижение производительности в Compose элементах и пишет автор статьи
👉 Берем Macrobenchmark (запуск сценариев), Perfetto (анализ и визуализация трассировок) и Diffetto (сравнения трассировок и определение что за элемент деградировал)
👉 Последовательно перекладываем данные из инструмента в инструмент и находим источник. В статье пример на маленьком приложении со сценарием замедления и тем как смотреть метрики
👉 В больших проектах множество
На мой взгляд, такой пошаговый анализ хорошо автоматизируется и может быть использован в качестве дополнительной проверки перед мержем задачки в главную ветку.
👉 Берем Macrobenchmark (запуск сценариев), Perfetto (анализ и визуализация трассировок) и Diffetto (сравнения трассировок и определение что за элемент деградировал)
👉 Последовательно перекладываем данные из инструмента в инструмент и находим источник. В статье пример на маленьком приложении со сценарием замедления и тем как смотреть метрики
👉 В больших проектах множество
@Composable
элементов, тут то Diffeto и выступает незаменимым помошником для фильтрации огромного количества трассировокНа мой взгляд, такой пошаговый анализ хорошо автоматизируется и может быть использован в качестве дополнительной проверки перед мержем задачки в главную ветку.
Автор просит об обратной связи по поводу статьи и библиотеки. Не стесняйтесь задавать вопросы в комментариях!
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - tamimattafi/krop: Kotlin Multiplatform library for Image Cropping with Compose Multiplatform.
Kotlin Multiplatform library for Image Cropping with Compose Multiplatform. - tamimattafi/krop