Kotlin Adept Notes
1.98K subscribers
67 photos
10 videos
113 links
Канал о разработке на Kotlin и обо всем, что с ним связано
По всем вопросам и рекламе: @ajiekcx
Download Telegram
Фоновая работа в Android и iOS

Бытует мнение, что iOS вообще не позволяет приложению выполнять какие-либо действия в фоне, но это не совсем так. В одном из наших Compose Multiplatform приложений необходимо было реализовать синхронизацию данных в фоне, и моему коллеге пришлось глубже разобраться в теме.

На текущий момент не существует хорошего решения для KMP-проектов, которое предоставляло бы общий API для работы с фоновыми задачами. Это вполне объяснимо: API сильно отличаются между платформами.

🤖 Для решения задачи синхронизации данных в фоне в Android существует несколько решений, например, WorkManager, который имеет довольно удобный API и позволяет запускать задачи с интервалом не менее 15 минут. Он позволяет задать условия запуска задачи, порядок выполнения воркеров и определить поведение при повторном планировании одной и той же задачи.

🍏 В iOS есть два стула: BGAppRefreshTaskRequest и BGProcessingTaskRequest.

Первый предназначен для относительно быстрых операций длительностью до 30 секунд и может выполняться чаще, второй — для более долгих задач, которые могут выполняться в течение нескольких минут и даже часов. Разумеется, можно указать минимальное время, через которое должна быть выполнена синхронизация. В интернетах рекомендуют устанавливать интервал в один час, однако iOS конечно же не гарантирует, что задача будет выполнена вообще 🙃

С появлением SwiftUI стало удобнее работать с фоновыми задачами — достаточно запланировать их с помощью BGTaskScheduler и обрабатывать через модификатор backgroundTask. Однако, по сравнению с WorkManager, многое приходится делать вручную — например, явно обрабатывать ситуацию, когда задача уже запланирована, иначе интервал её запуска может быть сброшен.

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

#iOS #Android #Background #KMP
Please open Telegram to view this post
VIEW IN TELEGRAM
292
Хочу порекомендовать вам классный канал Dev Easy Notes про разработку на около-андроидную тематику и не только. Там можно найти много всего интересного:

🔘Серия постов о том, как работает Android
🔘Когда нужно использовать статику вместо DI
🔘Что не так с книгой "Чистый код"
🔘Самое странное собеседование
🔘Серия постов про то, как работает CI

На канале нет бесконечных репостов с Medium, зато есть качественный авторский контент с щепоткой хорошего юмора. Так что, если хотите прокачиваться не только в мобильной разработке, этот канал определённо будет вам полезен 😉
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥153😁22👾1
Расскажу историю про забавный факап с Unit-тестами — когда тесты не прогонялись, но никто этого не замечал.

Сейчас мы все такие модные и молодёжные: пишем KMP-проекты и запускаем тесты через Gradle-таску allTests, которая прогоняет все тесты в проекте и даже делает это на всех таргетах, если тесты написаны в общем коде.

В обычных Android-проектах для запуска Unit-тестов есть стандартная таска test, которая прогоняет тесты для всех BuildVariant. В своё время мы подумали: а зачем гонять тесты вхолостую, тратить больше времени? Достаточно же прогнать только релизную сборку — testReleaseUnitTest.

Но когда в проекте появляются Flavors (да, это зло, но иногда вынужденное), то для модуля app, где эти Flavors описаны, нужная таска превращается в test<Flavor><BuildType>UnitTest. Мы были уверены, что, запустив её, все тесты в других модулях тоже выполнятся. Но, конечно же, никто ничего не проверил 🌟

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

Звучит очень глупо — как можно было так ошибиться? Но когда ты видишь зелёный pipeline, в голову почему-то не приходит специально сломать какой-нибудь тест в отдельном модуле, чтобы проверить, что всё работает 🌟

Вот такая вот кринж-история. А какие факапы были у вас?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍32😁14
Must-have материалы для углубления в Compose

Если вы хотели глубже разобраться в Compose, то ловите список материалов в открытом доступе, которые точно заслуживают вашего внимания:

Доклады

- Мой доклад о том, что скрывает стейт в Compose
- Наш с Димой Григорьевым квиз по Compose
- Доклады от Димы о том, как работает позиционная мемоизация и как устроена композиция в Compose
- Доклад от Асахра Айдарова про устройство компиляторного плагина в Compose
- Доклад от Алексея Гладкова про то, как работает Compose for iOS
- Доклад от Leland Richardson. Как работают модификаторы [en]
- Доклад про создание UI фреймворка для PowerPoint на основе Compose Runtime [en].
- Хардкорный доклад от Jake Wharton про запуск Compose на контроллере от настольной лампы [en].

Репозитории

- Пример реализации автоматических анимаций на основе состояния
- Репозиторий с реализацией анимаций разной сложности
- Compose для терминала от Jake Wharton

Блоги

- Compose Broadcast
- Mobile Compose
- Блог от Zach Klippenstein [en]

Книги

- Jetpack Compose Internals - Jorge Castillo

Ну и не забывайте про официальную документацию — там целая кладезь полезной инфы про Compose, которую часто просто копируют авторы различных статей на Medium.

Если вы знаете ещё какие-то интересные материалы про устройство Compose, то обязательно делитесь ссылками в комментариях ⬇️
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥46👍43❤‍🔥2
Жизнь программиста в Лондоне

🏝 Последние две недели я отдыхал в Лондоне, встретился с инженерами из разных компаний — от стартапов до бигтеха, вроде Amazon и 𝕏. И захотелось написать небольшой пост на тему: стоит ли вообще переезжать сюда инженеру или нет.

🇬🇧 Лондон — очень большой и красивый город, но в то же время очень дорогой. Например, здесь самый дорогой общественный транспорт в мире, а про стоимость покупки собственного жилья можно даже не говорить.

💸 Отсюда вытекает главный нюанс: чтобы хорошо жить здесь, нужно также хорошо зарабатывать. Средняя зарплата программиста здесь — около £50k / y, которой едва хватит, чтобы снимать жильё и питаться. А чтобы найти хотя бы приемлемые для жизни 100k, не в бигтехе, нужно сильно постараться.

🤔Переезжают в Лондон обычно по двум типам виз: либо Skilled Worker, либо Global Talent Visa. В первом случае вы привязаны к работодателю, во втором у вас больше свободы, и при поиске работы вы будете конкурировать с местными, а не со всем миром.

👑 Так что, если решить финансовые вопросы, это отличный город для жизни. Здесь множество зелёных парков, красивые улицы, зрелищные постановки в театрах, приятный климат и многое другое. Но, разумеется, есть и минусы: не самые лучшие сервисы, средняя местная еда, отсутствие детских площадок и относительно высокий уровень преступности в некоторых районах.

А вы бы переехали в UK?
Please open Telegram to view this post
VIEW IN TELEGRAM
😐16👍12👎8🤡6🤔42🔥1😁1
Как подружить LifecycleOwner и Decompose

Некоторые API в Jetpack библиотеках принимают в качестве параметра LifecycleOwner, например, так сделано в CameraX. Однако если в вашем проекте используется Decompose, и вы используете LocalLifecycleOwner для получения текущего значения в Composable функции, то жизненный цикл будет работать некорректно: он будет соответствовать жизненному циклу Activity или Fragment, так как Decompose нигде не переопределяет этот CompositionLocal и использует собственный жизненный цикл из библиотеки Essenty.

Чтобы исправить эту проблему, необходимо сконвертировать LifecycleOwner из Decompose в его аналог из Jetpack. Однако из коробки пока что такого адаптера нет, и придётся написать его самостоятельно, по аналогии с конвертацией ЖЦ в Essenty.

В версии Decompose 3.4.0 эта проблема будет решаться проще: появится JetpackComponentContext, как отдельная зависимость, и можно будет сразу получить нужный lifecycle прямо из компонента.

Поэтому будьте внимательны при использовании CompositionLocal для работы с жизненным циклом, если навигация в проекте построена на Decompose.

#Decompose #Lifecycle
👍161
Время выглянуть за рамки мониторов и взять в руки удочку

Сделайте паузу от тасков и митов на летнем IT-фестивале от Selectel против выгорания!

🗓 27 июля
📍 Флагшток, Санкт-Петербург или онлайн


В программе:
- доклады и воркшопы о том, как встроить отдых в свой плотный график,
- жизненные выступления на IT-стендапе,
- возможность попробовать разные активности, чтобы найти новое хобби: скалолазание, бокс, кастом вещей, рыбалка

Участие бесплатное, нужно просто зарегистрироваться: https://slc.tl/kmc8s

А чтобы посмотреть полную программу, заглянуть за кулисы подготовки и поучаствовать в розыгрыше лимитированного тирекса, подписывайтесь на @Selectel_Events
👍2🔥1
Как встроить SwiftUI в Compose Multiplatform

Обычно я стараюсь избегать использования кастомных CompositionLocal в Compose, так как это добавляет неявные зависимости, и если не предоставить значение, приложение упадёт в рантайме. Я придерживаюсь подхода, в котором CompositionLocal можно использовать только тогда, когда значение действительно может быть полезно любой Composable-функции в дереве. Яркий пример — тема приложения.

И при работе с Compose Multiplatform я подсмотрел классное применение этого механизма для встраивания SwiftUI вьюшек в Composable функции.

1. В сорсете iosMain создаём CompositionLocal и интерфейс NativeViewFactory.
2. На стороне Swift реализуем этот интерфейс и передаём его в функцию создания UIViewController.
3. В этой функции пробрасываем фабрику через CompositionLocalProvider.
4. Далее в любом месте поддерева в iosMain можно получить доступ к этой нативной вьюшке.

🌐 Посмотреть пример приложения для сканирования QR-кодов с этим подходом можно в репозитории, который я подготовил для лекции в онлайн-университете.

#Compose #SwiftUI
Please open Telegram to view this post
VIEW IN TELEGRAM
❤‍🔥10🔥5👍21
Генерация сетевых моделей

Сейчас AI используют везде, где только можно и довольно часто можно встретить в опросах, что AI применяют для преобразования JSON в сетевые модели для взаимодействия с API бэкенда. Однако эту задачу довольно легко решает OpenAPI Generator, при условии, что бэкендеры сделали нормальную документацию, конечно 😁

Использование автогенерации моделей по спеке OpenAPI даёт сразу несколько преимуществ:
🟢 Вы не тратите время на написание бойлерплейт кода
🟢 Исключаете человеческий фактор, например, можно случайно забыть сделать поле nullable или наоборот
🟢 Всегда поддерживаете API-сущности в актуальном состоянии, так как спецификация OpenAPI напрямую связана с кодом бэкенда (ну, почти 🙃)

При этом благодаря Mustache шаблонам можно реализовать очень гибкое решение:
🟡 Использовать любую библиотеку для сериализации
🟡 Сразу маппить данные в нужные типы, например, в Instant из kotlinx-datetime
🟡 Поддерживать полиморфную сериализацию, когда ответы от API могут сильно различаться
🟡 И даже генерировать не только модели, но и код для взаимодействия с HTTP-клиентом, например, с Ktor

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

💭 А как вы создаете API-сущности в своих проектах?

#Network #OpenAPI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥421
Мы на работе не раз сталкивались с проблемой мониторинга и ответов на отзывы в разных магазинах приложений, но теперь появилось удобное решение прямо в Telegram!

С помощью MobileReviewsBot вы сможете мониторить отзывы в Google Play и App Store!

🔘Отслеживать все оценки и комментарии в одном месте
🔘Отвечать прямо из Telegram – без лишних действий
🔘Генерировать идеальные ответы с помощью встроенного ИИ

Сейчас проходит бета-тестирование бота, и вы можете принять участие:

🔘Что нужно сделать?
1. Начните пользоваться ботом прямо сейчас.
2. Через неделю оставьте отзыв командой /feedback.

🔘 Что вы получаете?
😀 Сразу — тариф «Плюс» на 1 месяц (без ограничений!)
😀 После отзыва — ещё 3 месяца бесплатно!

👉 Подключайтесь сейчас
Please open Telegram to view this post
VIEW IN TELEGRAM
🤡15👍91🔥1
Советы по сканированию DataMatrix

Если вам интересно, чем закончился мой вайбкодинг по созданию сканера для распознавания всех видов DataMatrix (DM), то вот к чему я пришёл:

🟡 Google ML Kit оказался производительнее ZXing по нашим замерам. Осталась проблема с тем, что ML Kit не распознаёт белые DM на чёрном фоне.
🟡 Самое простое решение оказалось самым эффективным: для сканирования инвертированных DataMatrix нужно инвертировать Bitmap. Однако всё не так просто.
🟡 Самый эффективный способ инвертировать Bitmap — использовать ColorMatrix и нарисовать Bitmap на Canvas. Пример оставлю в комментариях.
🟡 Другая проблема заключалась в том, что CameraX отдаёт изображение в формате YUV, и стандартный метод конвертации ImageProxy в Bitmap может упасть с ошибкой UnsupportedOperationException. Тут два пути решения: написать свой конвертер или выставить в настройках камеры OutputImageFormat в формат RGBA.
🟡 А чтобы одновременно работать с обычными и инвертированными DM, лучше не использовать один ImageProxy, а сделать разные анализаторы и раскидывать кадры между ними по очереди, объединив их с помощью паттерна «Компоновщик».

#CameraX #GoogleMLKit
Please open Telegram to view this post
VIEW IN TELEGRAM
👍25🤔31
Как самому зашифровать SharedPreferences

Вы наверняка знаете, что решение от Google в виде EncryptedSharedPreferences уже давно Deprecated, а какой-то адекватной замены им так и не появилось. И что делать, если безопасники отказываются принимать оговорку, что префы может читать только само приложение, если на устройстве нет рута? ☹️

Остается только написать свое решение и, на самом деле, сделать это не сильно сложно. Для этого нам понадобится AndroidKeystore и Tink — open-source решение от Google для работы с криптографией, которое очень удобно в использовании.

Алгоритм получается следующий:
1. В AndroidKeystore создаем новый ключ, если его еще нет
2. В Tink генерируем KeysetHandle
3. На основе этих данных создаем encryptedKeyset средствами Tink и сохраняем его в SharedPreferences
4. Затем из keysetHandle достаем примитив AEAD, с помощью которого уже будем шифровать данные
5. PROFIT

В этой реализации главное учесть два момента:
🔘Обязательно удалять ключ из AndroidKeystore при очистке префов
🔘Разработать стратегию на случай, если encryptedKeyset в префах или ключа в Keystore не оказалось, иначе вы не сможете расшифровать ваши данные!

Ну а дальше уже дело техники, если тема интересна, то я постараюсь собрать сниппет с кодом 😉
Please open Telegram to view this post
VIEW IN TELEGRAM
👍65🔥5😁1👌1
Какие планы на 30 августа? Есть возможность попасть на JVM Day — профильную конференцию для разработчиков.

В планах:
— обсудить кейсы, нестандартные решения и инженерные практики;
— послушать доклады специалистов из Сбера, Т-Банка, Яндекса, 2ГИС, Squad, 01. tech;
— проводить сезон на афтепати в компании единомышленников.

Часть вырученных на мероприятии средств пойдет на поддержку региональных вузов.
Встреча пройдет в штаб-квартире Т-Банка, а узнать подробности и купить билеты можно тут
10🤡1😭1
Зашифрованные префы для MultiplatformSettings

Как и обещал, выкладываю в общий доступ реализацию шифрования SharedPreferences с помощью библиотеки Tink и AndroidKeystore.

В данном случае реализация сделана для библиотеки MultiplatformSettings, но вы легко можете адаптировать ее для обычных SharedPreferences.

⚠️ Специально выкладываю решение как gist, потому что никаких гарантий нет, используйте на свой страх и риск!

🌐 Исходный код здесь

P.S. За реализацию скажем спасибо Евгению Мельцайкину 😎
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥24
Просмотр Markdown файлов в Android Studio

Если вы, как и я, не знали, что в студии можно просматривать форматированные .md-файлы, то держите рецепт, как починить отображение:

1. Откройте Help → Find Action
2. Введите "Choose Boot Java Runtime for the IDE"
3. В выпадающем списке в поле New выберите runtime с поддержкой JCEF.
4. Перезагрузите IDE

Это особенно полезно при работе с AI агентом Junie, который генерит ответы в этом формате.

#Markdown #AndroidStudio
354👍2
🛶 Новый сезон Podlodka Android Crew стартует уже через два месяца, а значит, пора выбрать тему сезона и нам нужна ваша помощь выбрать лучшую.

Мы подготовили три потенциальных темы:

🛠 Тулинг: от AI до Я
- Расскажем как грамотно использовать AI-агенты
- Узнаем тайную магию дебаггера
- Поделимся must-have плагинами

🔋 Работа в фоне
- Узнаем как сделать музыкальный плеер
- Разберемся как работать в фоне в KMP
- Поговорим про новые фичи, вроде Live Updates

⚙️ Архитектура: AI, KMP, SDK
- Научимся проектировать SDK
- Посмотрим дебаты про BDUI
- Узнаем, чем занимается мобильный архитектор

📊 Пройдите, пожалуйста, опрос по ссылке.

А среди тех, кто пройдет опрос до 20 июля включительно, мы разыграем проходку на грядущий сезон 🎁
Please open Telegram to view this post
VIEW IN TELEGRAM
🤝15🔥3🫡1
Недавно рассказывал вам про MobileReviews бот, хочу получить от вас фидбек. Интересен ли вам этот сервис?
Anonymous Poll
41%
Не публикую приложения
10%
Я сам такой за день напишу...
31%
Отслеживаю отзывы в сторах самостоятельно
9%
Использую другое решение
19%
Интересный бот, посмотрю
Make Snackbar Great Again

В Compose реализация показа snackbar получилась не самой удобной, на каждом экране приходится:
🔘 Оборачивать контент в Scaffold
🔘 Вызывать suspend-функции для показа snackbar
🔘 Не забывать предусмотреть отмену предыдущего snackbar
🔘 Страдать из-за BottomSheet, так как там не получится использовать Scaffold, иначе он растянется на весь экран

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

Вот что я предлагаю:
🟢Отказаться от показа snackbar через Scaffold и написать простую логику с декларативным отображением snackbar с анимацией в зависимости от состояния.
🟢Чтобы snackbar не перекрывал лишние элементы интерфейса, можно написать кастомный модификатор в Compose, который будет запоминать верхнюю позицию элемента, к которому он применён, и добавлять неявный отступ к snackbar.

Таким образом, эта реализация не только решает проблемы выше, но и позволяет отображать snackbar как внизу, так и вверху экрана.

🔥 Если тема интересна, ставьте реакции и я попробую собрать пример с такой реализацией.

#Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥121🥴9👍84
📂 Папка с Android-блогерами

Мы решили заколлабиться с крутыми инженерами, которые ведут блоги об Android-разработке и делятся авторским контентом.

Добавляйте папку себе, если вам интересно послушать разные мнения и советы на повседневные темы разработки 😉
Please open Telegram to view this post
VIEW IN TELEGRAM
19😴3
📣 По вашим заявкам сделал небольшую библиотеку для декларативного показа снекбаров.

Преимущества библиотеки:
🟢 Полностью декларативный API, забудьте про one-time события и их обработку в LaunchedEffect.
🟢 Можно сделать как один глобальный обработчик, так и множество вложенных.
🟢 Благодаря встроенным модификаторам важный UI не будет перекрываться снекбаром.
🟢 Можно отображать снекбар как внизу, так и вверху экрана.
🟢 Поддерживаются любые типы сообщений, а не только строки.
🟢 Нет зависимости от Material, отображайте любой UI, какой захотите.
🟢 Настраиваемые анимации появления и скрытия снекбара.
🟢 Поддерживаются Android, iOS и JVM-таргеты.
🟢 Простая интеграция с Decompose.

🌐 Инструкцию по подключению, сэмпл и исходный код смотрите в репозитории.

Звёздочки на репозиторий и репосты приветствуются!

#Compose #Snackbar #KMP
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥68👍94🤔1