Kotlin Adept Notes
1.72K subscribers
62 photos
7 videos
99 links
Канал о разработке на Kotlin и обо всем, что с ним связано
По всем вопросам и рекламе: @ajiekcx
Download Telegram
Топ 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
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, где опишем некоторые параметры:

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/")
}


💡 Как видите, делается это довольно сложно, поэтому не рекомендую использовать сторонние iOS библиотеки в Kotlin коде, лучше в общем коде сделать интерфейс, а реализацию оставить нативной на каждой платформе.

А еще если в iOS проекте уже использовалась эта библиотека, но другой версии, то все конечно же развалится 😂

#KMP #Kotlin #iOS
@kotlin_adept
Please open Telegram to view this post
VIEW IN TELEGRAM
В публичный доступ опубликовали воркшоп от Павла Стрельченко по созданию плагинов для любых IDE от JetBrains 👩‍💻

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

💡 Почерпнуть идеи для плагинов и автоматизаций
🔮 Понять как мыслить при работе с незнакомым кодом
🐞 Увидеть как эффективно работать с дебаггером
✏️ Услышать полезные советы и многое другое

Приятного просмотра

#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
Мы в команде недавно подняли свой сервис автоматизации на Kotlin и реализовали там много всяких полезных штук:

🟣 Интеграция YouTrack и GitLab для автоматического перевода тасок в актуальное состояние и заполнения данных
🟣 Напоминание о забытых ревью
🟣 Уведомления о релизах
🟣 И другие полезные мелочи

🔖 Подробнее о наших процессах и как мы делали такой сервер читайте в статье.

〰️ И пишите в комментарии какие must have автоматизации есть в ваших процессах

#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
Please open Telegram to view this post
VIEW IN TELEGRAM
Полиморфная сериализация

Проблема в коде выше кроется в поле 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