Топ 4 незаметных бага при смене стека
Помните я похвастался, что переписал проект с RX на корутины без багов? Так вот я соврал😼
Ну или не совсем, так как баги не связаны напрямую с асинхронщиной, поэтому давайте посмотрим на топ незаметных багов при смене стека технологий.
1️⃣ Переходя с Rx на корутины у вас может сломаться Retrofit, при этом только для специфичных кейсов, когда в ответе приходит код 204, Retrofit начинает воспринимать успешный ответ как ошибку из-за отсутствия Body.
2️⃣ Переходя с Retrofit на Ktor проверьте все экстеншены для определения сетевых ошибок, зависимость на OkHttp у вас останется, но работать экстеншены перестанут так как исключение уже будет не HttpException, а ResponseException.
3️⃣ А также проверьте места, где возвращается Response, а не Body, в Retrofit это всегда успешная операция вне зависимости от ответа, в Ktor же это будет ошибкой, если выставлен флаг expectSuccess.
4️⃣ При переходе с GSON на KotlinxSerialization убедитесь, что все опциональные поля моделей помечены nullable и имеют дефолтное значение, так как GSON мог засунуть null даже в non-nullable поле и все могло работать, если к этому полю не обращаться.
💭 А какие незаметные баги просачивались у вас при рефакторинге стека технологий?
#Android #Kotlin
@kotlin_adept
Помните я похвастался, что переписал проект с RX на корутины без багов? Так вот я соврал
Ну или не совсем, так как баги не связаны напрямую с асинхронщиной, поэтому давайте посмотрим на топ незаметных багов при смене стека технологий.
#Android #Kotlin
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
iOS библиотеки в Kotlin коде
Не все знают, что в KMP мы не только можем скомпилировать Kotlin код в iOS фреймворк, но и, наоборот, использовать сторонние iOS библиотеки в Kotlin коде.
▶️ Например, есть две нативные библиотеки под Android и iOS, и вы хотите объединить их в одну KMP библиотеку и сделать все на Kotlin, тогда у вас есть два пути:
1️⃣ Использовать cocoapods (сторонний менеджер зависимостей в iOS), тогда подключение iOS фреймворка делается в одну строчку кода
2️⃣ Подключать фреймворк вручную, но тут все намного сложнее
Проблема cocoapods в том, что недавно их перевели в режим поддержки и большинство iOS разработчиков мигрируют свои зависимости на официальное решение SPM (Swift Package Manager), поэтому пойдем по второму пути и подключим iOS framework вручную:
1. Необходимо создать файл с расширением .def, где опишем некоторые параметры:
2. В build.gradle вашей библиотеки необходимо добавить конфигурацию для создания klib из фреймворка с помощью cinterop🤯 :
Сделать это необходимо для всех iOS таргетов❗️ Также как и в следующем шаге.
3. В build.gradle файле, где будете собирать итоговый фреймворк для iOS нужно прилинковать тот же фреймворк:
💡 Как видите, делается это довольно сложно, поэтому не рекомендую использовать сторонние iOS библиотеки в Kotlin коде, лучше в общем коде сделать интерфейс, а реализацию оставить нативной на каждой платформе.
❌ А еще если в iOS проекте уже использовалась эта библиотека, но другой версии, то все конечно же развалится 😂
#KMP #Kotlin #iOS
@kotlin_adept
Не все знают, что в KMP мы не только можем скомпилировать Kotlin код в iOS фреймворк, но и, наоборот, использовать сторонние iOS библиотеки в Kotlin коде.
Проблема cocoapods в том, что недавно их перевели в режим поддержки и большинство iOS разработчиков мигрируют свои зависимости на официальное решение SPM (Swift Package Manager), поэтому пойдем по второму пути и подключим iOS framework вручную:
1. Необходимо создать файл с расширением .def, где опишем некоторые параметры:
language = Objective-C
modules = YourFrameworkName
package = YourFrameworkName
2. В build.gradle вашей библиотеки необходимо добавить конфигурацию для создания klib из фреймворка с помощью cinterop
KotlinNativeTarget.compilations.getByName("main") {
val YourFramework by cinterops.creating {
defFile = project.file("src/nativeInterop/cinterop/YourFramework.def")
compilerOpts("-framework", "YourFramework", "-F${projectDir}/../YourFramework.xcframework/$frameworkArch/")
compilerOpts("-fmodules")
}
}
Сделать это необходимо для всех iOS таргетов
3. В build.gradle файле, где будете собирать итоговый фреймворк для iOS нужно прилинковать тот же фреймворк:
KotlinNativeTarget.binaries.all {
linkerOpts("-framework", "YourFramework", "-F${projectDir}/../YourFramework.xcframework/$frameworkArch/")
}
#KMP #Kotlin #iOS
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
В публичный доступ опубликовали воркшоп от Павла Стрельченко по созданию плагинов для любых IDE от JetBrains 👩💻
Очень рекомендую глянуть воркшоп, даже если вы не собираетесь разрабатывать плагины, ведь из воркшопа можно вынести много всего полезного, а именно:
💡 Почерпнуть идеи для плагинов и автоматизаций
🔮 Понять как мыслить при работе с незнакомым кодом
🐞 Увидеть как эффективно работать с дебаггером
✏️ Услышать полезные советы и многое другое
Приятного просмотра✅
#Plugins #Kotlin #IDE
@kotlin_adept
Очень рекомендую глянуть воркшоп, даже если вы не собираетесь разрабатывать плагины, ведь из воркшопа можно вынести много всего полезного, а именно:
Приятного просмотра
#Plugins #Kotlin #IDE
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
SQLDelight vs Exposed
Для моего бека на Ktor нужно было выбрать ORM для реляционной БД и я решил посмотреть на решение от JetBrains под названием Exposed.
Библиотека довольно интересная, но сильно непривычная, так как с SQL вообще не нужно контактировать, поверх него написан свой dsl на Kotlin, который не такой уж интуитивный и без документации никакой более менее сложный запрос не напишешь. Однако там хотя бы можно из коробки создавать таблицы, в отличие от SQLDelight, но при этом в Exposed напрочь отсутствует поддержка миграций.
Поэтому я решил сделать небольшое сравнение в изображении к этому посту 👆
По итогу я все же выбралSQLDelight в связке с Flyway для миграций и HikariCP для соединения с БД, так как этот инструмент уже привычный и его более чем достаточно для моих целей.
А что выбрали бы вы?
#SQLDelight #Exposed #ORM #Kotlin
@kotlin_adept
Для моего бека на Ktor нужно было выбрать ORM для реляционной БД и я решил посмотреть на решение от JetBrains под названием Exposed.
Библиотека довольно интересная, но сильно непривычная, так как с SQL вообще не нужно контактировать, поверх него написан свой dsl на Kotlin, который не такой уж интуитивный и без документации никакой более менее сложный запрос не напишешь. Однако там хотя бы можно из коробки создавать таблицы, в отличие от SQLDelight, но при этом в Exposed напрочь отсутствует поддержка миграций.
Поэтому я решил сделать небольшое сравнение в изображении к этому посту 👆
По итогу я все же выбрал
А что выбрали бы вы?
#SQLDelight #Exposed #ORM #Kotlin
@kotlin_adept
Мы в команде недавно подняли свой сервис автоматизации на Kotlin и реализовали там много всяких полезных штук:
🟣 Интеграция YouTrack и GitLab для автоматического перевода тасок в актуальное состояние и заполнения данных
🟣 Напоминание о забытых ревью
🟣 Уведомления о релизах
🟣 И другие полезные мелочи
🔖 Подробнее о наших процессах и как мы делали такой сервер читайте в статье.
〰️ И пишите в комментарии какие must have автоматизации есть в ваших процессах
#Kotlin #Ktor #Server
@kotlin_adept
#Kotlin #Ktor #Server
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
Интероп suspend и async функций
Ранее все suspend-функции в Kotlin превращались в обычные функции с completionHandler на стороне Swift, но начиная с Swift 5.5 появился интероп между корутинами в обе стороны. Однако пусть это не вводит вас в заблуждение: это всего лишь "сахар" в Swift, который преобразует коллбэки в асинхронные функции❗️
Соответственно, у вас будет работать базовый сценарий использования асинхронной функции, но на этом из хороших новостей всё:
❌ Запуск async-функции на Main-диспетчере не гарантирует выполнения на главном потоке в Swift.
❌ CancellationException не будет преобразовываться в CancellationError, соответственно, не будет работать кооперативная отмена корутин и есть риск получить work leak.
💡 Казалось бы, с этой проблемой может помочь библиотека SKIE, но и там не всё гладко. Она исправляет только интероп в одну сторону, когда мы вызываем suspend-функции из Swift-кода, но не в обратную.
Так что будьте осторожны с таким интеропом и следите за развитием полноценной поддержки в соответствующем issue.
#Coroutines #Kotlin #Swift
Ранее все suspend-функции в Kotlin превращались в обычные функции с completionHandler на стороне Swift, но начиная с Swift 5.5 появился интероп между корутинами в обе стороны. Однако пусть это не вводит вас в заблуждение: это всего лишь "сахар" в Swift, который преобразует коллбэки в асинхронные функции
Соответственно, у вас будет работать базовый сценарий использования асинхронной функции, но на этом из хороших новостей всё:
Так что будьте осторожны с таким интеропом и следите за развитием полноценной поддержки в соответствующем issue.
#Coroutines #Kotlin #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
Полиморфная сериализация
Проблема в коде выше кроется в поле type, так как это поле является дискриминатором по умолчанию для библиотеки kotlinx-serialization. Но что это значит?
При сериализации полиморфного типа в JSON добавляется специальное поле type с полным именем класса:
Это необходимо, чтобы при десериализации получить корректный подкласс. В данном случае дискриминатор конфликтует с нашим полем type, которое имеет тип UserType, из-за чего возникает ошибка при сериализации объекта:
Исправить ситуацию можно очень просто, и есть несколько вариантов:
- Никогда не использовать поле с именем type в полиморфных классах.
- Изменить название поля с помощью аннотации @SerialName.
- Изменить дискриминатор по умолчанию при конфигурации JSON-объекта. За это отвечает поле classDiscriminator.
#Kotlin #Serialization
Проблема в коде выше кроется в поле type, так как это поле является дискриминатором по умолчанию для библиотеки kotlinx-serialization. Но что это значит?
При сериализации полиморфного типа в JSON добавляется специальное поле type с полным именем класса:
{"type":"com.example.UserConfig.UserDetails","id":"1234"}
Это необходимо, чтобы при десериализации получить корректный подкласс. В данном случае дискриминатор конфликтует с нашим полем type, которое имеет тип UserType, из-за чего возникает ошибка при сериализации объекта:
val jsonString = """{ "id": "1234", "type": "Internal" }"""
val detailsConfig = json.decodeFromString<UserConfig.UserDetails>(jsonString)
json.encodeToString(UserConfig.serializer(), detailsConfig) // IllegalStateException
Исправить ситуацию можно очень просто, и есть несколько вариантов:
- Никогда не использовать поле с именем type в полиморфных классах.
- Изменить название поля с помощью аннотации @SerialName.
- Изменить дискриминатор по умолчанию при конфигурации JSON-объекта. За это отвечает поле classDiscriminator.
#Kotlin #Serialization
Зачем мигрировать на котлиновский 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