Зачем мигрировать на котлиновский UUID?
Вы, наверное, слышали, что в Kotlin появился встроенный генератор UUID, который можно использовать в общем коде. Но он всё ещё экспериментальный, да и вообще написать expect/actual функцию можно в одну строчку. Поэтому, думаю, многие даже и не задумывались о переходе в KMP-проектах. Но на самом деле это имеет смысл.
Проблема с подходом expect/actual заключается в том, что UUID под iOS будет генерироваться в верхнем регистре, и это может привести к проблемам. Например:
Представим, что вы работаете с чатом и сохраняете какое-то сообщение с неким ID в БД и отправляете его же на сервер. Но при следующей загрузке данных с бэкенда вам возвращается это сообщение с ID в нижнем регистре. Если вы использовали обычный SQL-запрос для поиска сообщения по ID, то ничего не найдёте, потому что регистр отличается. В то время как на Android всё будет работать корректно.
Таким образом, использование нового механизма генерации поможет избежать этой проблемы, так как UUID будет генерироваться в одном виде на всех платформах. Помимо этого вы получаете лучшую типобезопасность, так как можете вместо String использовать Uuid для всех идентификаторов в вашем коде.
#KMP #Kotlin
Вы, наверное, слышали, что в Kotlin появился встроенный генератор UUID, который можно использовать в общем коде. Но он всё ещё экспериментальный, да и вообще написать expect/actual функцию можно в одну строчку. Поэтому, думаю, многие даже и не задумывались о переходе в KMP-проектах. Но на самом деле это имеет смысл.
Проблема с подходом expect/actual заключается в том, что UUID под iOS будет генерироваться в верхнем регистре, и это может привести к проблемам. Например:
Представим, что вы работаете с чатом и сохраняете какое-то сообщение с неким ID в БД и отправляете его же на сервер. Но при следующей загрузке данных с бэкенда вам возвращается это сообщение с ID в нижнем регистре. Если вы использовали обычный SQL-запрос для поиска сообщения по ID, то ничего не найдёте, потому что регистр отличается. В то время как на Android всё будет работать корректно.
Таким образом, использование нового механизма генерации поможет избежать этой проблемы, так как UUID будет генерироваться в одном виде на всех платформах. Помимо этого вы получаете лучшую типобезопасность, так как можете вместо String использовать Uuid для всех идентификаторов в вашем коде.
#KMP #Kotlin
👍28
Обычно SwiftUI и Compose очень похожи между собой, и, как правило, стейт для экранов можно формировать одинаково — это очень удобно при работе с KMP.
Но иногда бывают сильные отличия в API, например, PullToRefresh: если в Compose индикатор показывается по изменению состояния, то в SwiftUI — это асинхронная таска🤬
Недавно мы наткнулись на ещё один такой компонент — контекстное меню. На слайде видно, насколько более громоздко выглядит код на Compose, но не все так однозначно. Здесь разница в том, что, опять же, если в Compose меню показывается в зависимости от состояния, то в SwiftUI мы сразу должны знать, какие элементы отображать в этом меню, и нельзя сделать это по клику. Это неудобно, если элементы контекстного меню формируются динамически.
Чтобы исправить это, придётся в стейте сразу хранить словарь и в зависимости от типа ячейки, на которую кликнули, выбирать нужный набор значений.
💬 А какие ошибки допускали вы при формировании стейта экрана?
#Compose #SwiftUI
Но иногда бывают сильные отличия в API, например, PullToRefresh: если в Compose индикатор показывается по изменению состояния, то в SwiftUI — это асинхронная таска
Недавно мы наткнулись на ещё один такой компонент — контекстное меню. На слайде видно, насколько более громоздко выглядит код на Compose, но не все так однозначно. Здесь разница в том, что, опять же, если в Compose меню показывается в зависимости от состояния, то в SwiftUI мы сразу должны знать, какие элементы отображать в этом меню, и нельзя сделать это по клику. Это неудобно, если элементы контекстного меню формируются динамически.
Чтобы исправить это, придётся в стейте сразу хранить словарь и в зависимости от типа ячейки, на которую кликнули, выбирать нужный набор значений.
#Compose #SwiftUI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍20🔥3
Маст-хэв кастомные Gradle-плагины
Если вы разрабатываете несколько приложений, то наверняка уже шарите какой-то общий код, вынося его в отдельные библиотеки или модули. Но не только общие модули могут быть полезны — можно ещё переиспользовать утилитарный код с помощью Gradle-плагинов.
Знаю, что многие, мягко говоря, не любят Gradle — и на то есть причины. Однако написание собственного небольшого плагина проще, чем кажется, особенно если делать это неправильно (привет afterEvaluate🙃 ).
Вот несколько идей для плагинов, которые могут быть полезны:
1️⃣ Version Catalog — очень простой плагин, который помогает удобно переиспользовать версии зависимостей между проектами. Про него я уже как-то писал здесь.
2️⃣ Code Style — обёртка над Detekt с настроенными дефолтными и кастомными правилами.
3️⃣ Git Hook — запуск тестов и проверки кода при коммите, проверка сообщения коммита и прочее.
4️⃣ Vault — плагин для получения секретов из защищённого хранилища при первоначальной настройке проекта.
5️⃣ Publish — плагин для упрощения публикации собственных плагинов, каталога версий, Android и KMP-библиотек.
🔥 Если тема интересна — ставьте реакции, и я расскажу подробнее, как создавать такие плагины и дам рекомендации как не стоит делать.
#Gradle
Если вы разрабатываете несколько приложений, то наверняка уже шарите какой-то общий код, вынося его в отдельные библиотеки или модули. Но не только общие модули могут быть полезны — можно ещё переиспользовать утилитарный код с помощью Gradle-плагинов.
Знаю, что многие, мягко говоря, не любят Gradle — и на то есть причины. Однако написание собственного небольшого плагина проще, чем кажется, особенно если делать это неправильно (привет afterEvaluate
Вот несколько идей для плагинов, которые могут быть полезны:
#Gradle
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥86👍13💯3
Действительно, стало гораздо проще адаптировать какой-нибудь простой Jetpack Compose-пример на iOS, буквально перенося файлы из одной папки в другую, но с реальными приложениями всё не так гладко. И вот какие проблемы я вижу на текущий момент:
Во Flutter, например, есть огромное количество библиотек на любой вкус и цвет, которые покрывают все платформенные API в общем коде: работу с разрешениями, камерой, геолокацией и другими. В CMP же в большинстве случаев придётся реализовывать это нативно, что требует хотя бы минимальных знаний платформы и языка.
Сейчас «из коробки» доступны только Material-виджеты, и, несмотря на то что у многих приложений своя дизайн-система, всё равно хотелось бы адаптировать часть виджетов под платформу. Например, Android-овский PullToRefresh выглядит максимально инородно на iOS и в целом плохо дружит с физикой скролла на iOS.
В анонсе сказано, что производительность CMP сравнима со SwiftUI и, судя по графикам, даже превосходит его. Но это всего лишь один бенчмарк ленивого списка. Если вы начнёте сравнивать приложение на SwiftUI и CMP на каком-нибудь iPhone 13, то невооружённым глазом увидите разницу не в пользу Compose. Очевидно, что проблема кроется в Skia, от которой Flutter и отказался из-за проблем с производительностью. Будем надеяться, что в будущем команда CMP тоже предпримет какие-то шаги в этом направлении.
#ComposeMultiplatform #CMP
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥39👍5❤1
Каждый раз, когда приходится обновлять версии Kotlin и Compose, я чувствую себя как тот мужик из мема.
Обновить зависимости так, чтобы ничего не отвалилось, та ещё задача, а в KMP-проектах добавляется ещё больше веселья.
Например, хотим запустить проект на Xcode 16.3 — для этого нужен Kotlin 2.1.21, а для него требуется новый KSP, в котором сломали обратную совместимость. И какая-нибудь либа в проекте, использующая KSP, благополучно перестаёт работать.
К счастью, отключить вторую версию KSP можно в
Хуже этого только борьба с Java-версиями. Когда в каком-нибудь кастомном detekt-правиле забыли указать👍
#Gradle #KSP
Обновить зависимости так, чтобы ничего не отвалилось, та ещё задача, а в KMP-проектах добавляется ещё больше веселья.
Например, хотим запустить проект на Xcode 16.3 — для этого нужен Kotlin 2.1.21, а для него требуется новый KSP, в котором сломали обратную совместимость. И какая-нибудь либа в проекте, использующая KSP, благополучно перестаёт работать.
К счастью, отключить вторую версию KSP можно в
gradle.properties
, и это спасёт на какое-то время:ksp.useKSP2=false
Хуже этого только борьба с Java-версиями. Когда в каком-нибудь кастомном detekt-правиле забыли указать
jvmToolchain
с нужной версией, приключение на весь день точно будет обеспечено #Gradle #KSP
Please open Telegram to view this post
VIEW IN TELEGRAM
UI-тестирование в Compose 🎨
Если вы хотите реализовать UI-тесты в Compose, то на сегодняшний день есть три основных решения: официальная библиотека от Google и обёртки над ней — Kakao/Kaspresso и Ultron. Про официальное решение сегодня говорить не будем, а рассмотрим поближе другие библиотеки.
Мы уже много лет используем Kaspresso для UI-тестов, но недавно я решил поближе познакомиться с библиотекой Ultron и сравнить оба решения. На самом деле, по функциональности они очень схожи.
🟡 Ultron выглядит довольно заманчиво: у него есть два ключевых преимущества перед конкурентами — при написании тестов на Ultron практически отсутствует какой-либо бойлерплейт-код, а также Ultron поддерживает Compose Multiplatform. Ещё из плюсов можно выделить качественную документацию. Из недостатков я бы отметил довольно небольшое комьюнити у библиотеки, а также, лично для меня, оказалось непривычным отсутствие связей parent-child между PageObject. Подробнее узнать про Ultron можно в недавно опубликованном докладе от автора библиотеки — Алексея Тюрина.
🔘 Kakao/Kaspresso — проверенные временем библиотеки с большим комьюнити, но описание PageObject в Kakao до версии 1.0 требовало довольно много бойлерплейт-кода, особенно при работе с Lazy-списками. Также механизм flaky safety в Kaspresso не совсем хорошо работает с виртуальным временем в Compose-тестах. Об этом подробно рассказал Паша Стрельченко в своём докладе.
💬 А что используете вы для UI-тестирования в Compose? И оправдан ли тренд на уход от дорогих UI-тестов в проекте?
#Compose #UiTesting
Если вы хотите реализовать UI-тесты в Compose, то на сегодняшний день есть три основных решения: официальная библиотека от Google и обёртки над ней — Kakao/Kaspresso и Ultron. Про официальное решение сегодня говорить не будем, а рассмотрим поближе другие библиотеки.
Мы уже много лет используем Kaspresso для UI-тестов, но недавно я решил поближе познакомиться с библиотекой Ultron и сравнить оба решения. На самом деле, по функциональности они очень схожи.
💬 А что используете вы для UI-тестирования в Compose? И оправдан ли тренд на уход от дорогих UI-тестов в проекте?
#Compose #UiTesting
Please open Telegram to view this post
VIEW IN TELEGRAM
👍17
Решил я, значит, повайбкодить и сделать CLI-приложение на Kotlin для сканирования Data Matrix с помощью библиотеки ZXing. На вход бы передавался архив с датасетом из реальных изображений продуктов с кодами, а на выходе получали бы отчёт: какие Data Matrix удалось распарсить, а какие — нет.
Первую версию я сделал с помощью ChatGPT, и, несмотря на то что модельки стали писать рабочий код с первого раза, результат получился катастрофическим — распозналось всего 10% Data Matrix из всего датасета на тысячи кодов. Зато работало это довольно быстро.
Далее я попробовал все последние модели, доступные без платной подписки: GPT-4o, Gemini 2.5 Flash, Deepseek R1, Claude Sonnet 4 и Grok 3. Попросил их улучшить текущее распознавание кодов.
В итоге во всех случаях я получил решение, работающее в сотню раз медленнее, которое либо давало тот же результат, либо вообще ничего не сканировало🤡
Единственным, кто смог хоть что-то улучшить со второй попытки, оказался Grok — но и то разница получилась не драматической: условно, из 20 кодов распозналось не 2, а 4.
Так что на этом мой вайбкодинг закончился — придётся дальше всё делать самому🥲
Первую версию я сделал с помощью ChatGPT, и, несмотря на то что модельки стали писать рабочий код с первого раза, результат получился катастрофическим — распозналось всего 10% Data Matrix из всего датасета на тысячи кодов. Зато работало это довольно быстро.
Далее я попробовал все последние модели, доступные без платной подписки: GPT-4o, Gemini 2.5 Flash, Deepseek R1, Claude Sonnet 4 и Grok 3. Попросил их улучшить текущее распознавание кодов.
В итоге во всех случаях я получил решение, работающее в сотню раз медленнее, которое либо давало тот же результат, либо вообще ничего не сканировало
Единственным, кто смог хоть что-то улучшить со второй попытки, оказался Grok — но и то разница получилась не драматической: условно, из 20 кодов распозналось не 2, а 4.
Так что на этом мой вайбкодинг закончился — придётся дальше всё делать самому
Please open Telegram to view this post
VIEW IN TELEGRAM
🤣34❤4😁2😢2
Фоновая работа в 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
Бытует мнение, что iOS вообще не позволяет приложению выполнять какие-либо действия в фоне, но это не совсем так. В одном из наших Compose Multiplatform приложений необходимо было реализовать синхронизацию данных в фоне, и моему коллеге пришлось глубже разобраться в теме.
Первый предназначен для относительно быстрых операций длительностью до 30 секунд и может выполняться чаще, второй — для более долгих задач, которые могут выполняться в течение нескольких минут и даже часов. Разумеется, можно указать минимальное время, через которое должна быть выполнена синхронизация. В интернетах рекомендуют устанавливать интервал в один час, однако iOS конечно же не гарантирует, что задача будет выполнена вообще
С появлением SwiftUI стало удобнее работать с фоновыми задачами — достаточно запланировать их с помощью BGTaskScheduler и обрабатывать через модификатор backgroundTask. Однако, по сравнению с WorkManager, многое приходится делать вручную — например, явно обрабатывать ситуацию, когда задача уже запланирована, иначе интервал её запуска может быть сброшен.
#iOS #Android #Background #KMP
Please open Telegram to view this post
VIEW IN TELEGRAM
✍29❤2
Хочу порекомендовать вам классный канал Dev Easy Notes про разработку на около-андроидную тематику и не только. Там можно найти много всего интересного:
🔘 Серия постов о том, как работает Android
🔘 Когда нужно использовать статику вместо DI
🔘 Что не так с книгой "Чистый код"
🔘 Самое странное собеседование
🔘 Серия постов про то, как работает CI
На канале нет бесконечных репостов с Medium, зато есть качественный авторский контент с щепоткой хорошего юмора. Так что, если хотите прокачиваться не только в мобильной разработке, этот канал определённо будет вам полезен😉
На канале нет бесконечных репостов с Medium, зато есть качественный авторский контент с щепоткой хорошего юмора. Так что, если хотите прокачиваться не только в мобильной разработке, этот канал определённо будет вам полезен
Please open Telegram to view this post
VIEW IN TELEGRAM
Telegram
Dev Easy Notes
Работаю в IT уже 8 лет. Рассказываю про разработку простым языком. Полезность скрыта под тупыми шутками и слоем мата. Лучший underground канал про разработку, который вы сможете найти.
По сотрудничеству писать @haroncode
По сотрудничеству писать @haroncode
🔥15❤3😁2 2👾1
Расскажу историю про забавный факап с Unit-тестами — когда тесты не прогонялись, но никто этого не замечал.
Сейчас мы все такие модные и молодёжные: пишем KMP-проекты и запускаем тесты через Gradle-таску
В обычных Android-проектах для запуска Unit-тестов есть стандартная таска
Но когда в проекте появляются Flavors (да, это зло, но иногда вынужденное), то для модуля app, где эти Flavors описаны, нужная таска превращается в🌟
А если в других модулях Flavors нет, то тесты по этой таске просто не прогоняются! Для них нужно явно вызывать таску без Flavor.
Звучит очень глупо — как можно было так ошибиться? Но когда ты видишь зелёный pipeline, в голову почему-то не приходит специально сломать какой-нибудь тест в отдельном модуле, чтобы проверить, что всё работает🌟
Вот такая вот кринж-история. А какие факапы были у вас?
Сейчас мы все такие модные и молодёжные: пишем 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, то обязательно делитесь ссылками в комментариях⬇️
Если вы хотели глубже разобраться в 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👍4❤3❤🔥2
Жизнь программиста в Лондоне
🏝 Последние две недели я отдыхал в Лондоне, встретился с инженерами из разных компаний — от стартапов до бигтеха, вроде Amazon и 𝕏. И захотелось написать небольшой пост на тему: стоит ли вообще переезжать сюда инженеру или нет.
🇬🇧 Лондон — очень большой и красивый город, но в то же время очень дорогой. Например, здесь самый дорогой общественный транспорт в мире, а про стоимость покупки собственного жилья можно даже не говорить.
💸 Отсюда вытекает главный нюанс: чтобы хорошо жить здесь, нужно также хорошо зарабатывать. Средняя зарплата программиста здесь — около £50k / y, которой едва хватит, чтобы снимать жильё и питаться. А чтобы найти хотя бы приемлемые для жизни 100k, не в бигтехе, нужно сильно постараться.
🤔 Переезжают в Лондон обычно по двум типам виз: либо Skilled Worker, либо Global Talent Visa. В первом случае вы привязаны к работодателю, во втором у вас больше свободы, и при поиске работы вы будете конкурировать с местными, а не со всем миром.
👑 Так что, если решить финансовые вопросы, это отличный город для жизни. Здесь множество зелёных парков, красивые улицы, зрелищные постановки в театрах, приятный климат и многое другое. Но, разумеется, есть и минусы: не самые лучшие сервисы, средняя местная еда, отсутствие детских площадок и относительно высокий уровень преступности в некоторых районах.
А вы бы переехали в UK?
🇬🇧 Лондон — очень большой и красивый город, но в то же время очень дорогой. Например, здесь самый дорогой общественный транспорт в мире, а про стоимость покупки собственного жилья можно даже не говорить.
А вы бы переехали в UK?
Please open Telegram to view this post
VIEW IN TELEGRAM
😐16👍12👎8🤡6🤔4❤2🔥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
Некоторые 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
👍16❤1
Время выглянуть за рамки мониторов и взять в руки удочку
Сделайте паузу от тасков и митов на летнем IT-фестивале от Selectel против выгорания!
🗓 27 июля
📍 Флагшток, Санкт-Петербург или онлайн
В программе:
- доклады и воркшопы о том, как встроить отдых в свой плотный график,
- жизненные выступления на IT-стендапе,
- возможность попробовать разные активности, чтобы найти новое хобби: скалолазание, бокс, кастом вещей, рыбалка
Участие бесплатное, нужно просто зарегистрироваться: https://slc.tl/kmc8s
А чтобы посмотреть полную программу, заглянуть за кулисы подготовки и поучаствовать в розыгрыше лимитированного тирекса, подписывайтесь на @Selectel_Events
Сделайте паузу от тасков и митов на летнем 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
Обычно я стараюсь избегать использования кастомных CompositionLocal в Compose, так как это добавляет неявные зависимости, и если не предоставить значение, приложение упадёт в рантайме. Я придерживаюсь подхода, в котором CompositionLocal можно использовать только тогда, когда значение действительно может быть полезно любой Composable-функции в дереве. Яркий пример — тема приложения.
И при работе с Compose Multiplatform я подсмотрел классное применение этого механизма для встраивания SwiftUI вьюшек в Composable функции.
1. В сорсете iosMain создаём CompositionLocal и интерфейс NativeViewFactory.
2. На стороне Swift реализуем этот интерфейс и передаём его в функцию создания UIViewController.
3. В этой функции пробрасываем фабрику через CompositionLocalProvider.
4. Далее в любом месте поддерева в iosMain можно получить доступ к этой нативной вьюшке.
#Compose #SwiftUI
Please open Telegram to view this post
VIEW IN TELEGRAM
❤🔥10🔥5👍2❤1
Генерация сетевых моделей
Сейчас AI используют везде, где только можно и довольно часто можно встретить в опросах, что AI применяют для преобразования JSON в сетевые модели для взаимодействия с API бэкенда. Однако эту задачу довольно легко решает OpenAPI Generator, при условии, что бэкендеры сделали нормальную документацию, конечно😁
Использование автогенерации моделей по спеке OpenAPI даёт сразу несколько преимуществ:
🟢 Вы не тратите время на написание бойлерплейт кода
🟢 Исключаете человеческий фактор, например, можно случайно забыть сделать поле nullable или наоборот
🟢 Всегда поддерживаете API-сущности в актуальном состоянии, так как спецификация OpenAPI напрямую связана с кодом бэкенда (ну, почти 🙃 )
При этом благодаря Mustache шаблонам можно реализовать очень гибкое решение:
🟡 Использовать любую библиотеку для сериализации
🟡 Сразу маппить данные в нужные типы, например, в Instant из kotlinx-datetime
🟡 Поддерживать полиморфную сериализацию, когда ответы от API могут сильно различаться
🟡 И даже генерировать не только модели, но и код для взаимодействия с HTTP-клиентом, например, с Ktor
Мы у себя уже довольно давно обкатали этот подход и остались довольны, сейчас распространям эту практику и на другие проекты.
💭 А как вы создаете API-сущности в своих проектах?
#Network #OpenAPI
Сейчас AI используют везде, где только можно и довольно часто можно встретить в опросах, что AI применяют для преобразования JSON в сетевые модели для взаимодействия с API бэкенда. Однако эту задачу довольно легко решает OpenAPI Generator, при условии, что бэкендеры сделали нормальную документацию, конечно
Использование автогенерации моделей по спеке OpenAPI даёт сразу несколько преимуществ:
При этом благодаря Mustache шаблонам можно реализовать очень гибкое решение:
Мы у себя уже довольно давно обкатали этот подход и остались довольны, сейчас распространям эту практику и на другие проекты.
#Network #OpenAPI
Please open Telegram to view this post
VIEW IN TELEGRAM
👍11🔥4❤2 1
Мы на работе не раз сталкивались с проблемой мониторинга и ответов на отзывы в разных магазинах приложений, но теперь появилось удобное решение прямо в Telegram!
С помощью MobileReviewsBot вы сможете мониторить отзывы в Google Play и App Store!
🔘 Отслеживать все оценки и комментарии в одном месте
🔘 Отвечать прямо из Telegram – без лишних действий
🔘 Генерировать идеальные ответы с помощью встроенного ИИ
Сейчас проходит бета-тестирование бота, и вы можете принять участие:
🔘 Что нужно сделать?
1. Начните пользоваться ботом прямо сейчас.
2. Через неделю оставьте отзыв командой /feedback.
🔘 Что вы получаете?
😀 Сразу — тариф «Плюс» на 1 месяц (без ограничений!)
😀 После отзыва — ещё 3 месяца бесплатно!
👉 Подключайтесь сейчас
С помощью MobileReviewsBot вы сможете мониторить отзывы в Google Play и App Store!
Сейчас проходит бета-тестирование бота, и вы можете принять участие:
1. Начните пользоваться ботом прямо сейчас.
2. Через неделю оставьте отзыв командой /feedback.
👉 Подключайтесь сейчас
Please open Telegram to view this post
VIEW IN TELEGRAM
🤡15👍9❤1🔥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
Если вам интересно, чем закончился мой вайбкодинг по созданию сканера для распознавания всех видов DataMatrix (DM), то вот к чему я пришёл:
#CameraX #GoogleMLKit
Please open Telegram to view this post
VIEW IN TELEGRAM
👍25🤔3❤1
Как самому зашифровать 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 не оказалось, иначе вы не сможете расшифровать ваши данные!
Ну а дальше уже дело техники, если тема интересна, то я постараюсь собрать сниппет с кодом😉
Вы наверняка знаете, что решение от Google в виде EncryptedSharedPreferences уже давно Deprecated, а какой-то адекватной замены им так и не появилось. И что делать, если безопасники отказываются принимать оговорку, что префы может читать только само приложение, если на устройстве нет рута?
Остается только написать свое решение и, на самом деле, сделать это не сильно сложно. Для этого нам понадобится AndroidKeystore и Tink — open-source решение от Google для работы с криптографией, которое очень удобно в использовании.
Алгоритм получается следующий:
1. В AndroidKeystore создаем новый ключ, если его еще нет
2. В Tink генерируем KeysetHandle
3. На основе этих данных создаем encryptedKeyset средствами Tink и сохраняем его в SharedPreferences
4. Затем из keysetHandle достаем примитив AEAD, с помощью которого уже будем шифровать данные
5. PROFIT
В этой реализации главное учесть два момента:
Ну а дальше уже дело техники, если тема интересна, то я постараюсь собрать сниппет с кодом
Please open Telegram to view this post
VIEW IN TELEGRAM
👍65🔥5😁1👌1
Какие планы на 30 августа? Есть возможность попасть на JVM Day — профильную конференцию для разработчиков.
В планах:
— обсудить кейсы, нестандартные решения и инженерные практики;
— послушать доклады специалистов из Сбера, Т-Банка, Яндекса, 2ГИС, Squad, 01. tech;
— проводить сезон на афтепати в компании единомышленников.
Часть вырученных на мероприятии средств пойдет на поддержку региональных вузов.
Встреча пройдет в штаб-квартире Т-Банка, а узнать подробности и купить билеты можно тут
В планах:
— обсудить кейсы, нестандартные решения и инженерные практики;
— послушать доклады специалистов из Сбера, Т-Банка, Яндекса, 2ГИС, Squad, 01. tech;
— проводить сезон на афтепати в компании единомышленников.
Часть вырученных на мероприятии средств пойдет на поддержку региональных вузов.
Встреча пройдет в штаб-квартире Т-Банка, а узнать подробности и купить билеты можно тут
Зашифрованные префы для MultiplatformSettings
Как и обещал, выкладываю в общий доступ реализацию шифрования SharedPreferences с помощью библиотеки Tink и AndroidKeystore.
В данном случае реализация сделана для библиотеки MultiplatformSettings, но вы легко можете адаптировать ее для обычных SharedPreferences.
⚠️ Специально выкладываю решение как gist, потому что никаких гарантий нет, используйте на свой страх и риск!
🌐 Исходный код здесь
P.S. За реализацию скажем спасибо Евгению Мельцайкину😎
Как и обещал, выкладываю в общий доступ реализацию шифрования SharedPreferences с помощью библиотеки Tink и AndroidKeystore.
В данном случае реализация сделана для библиотеки MultiplatformSettings, но вы легко можете адаптировать ее для обычных SharedPreferences.
P.S. За реализацию скажем спасибо Евгению Мельцайкину
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥24