Kotlin Adept Notes
1.98K subscribers
67 photos
10 videos
114 links
Канал о разработке на Kotlin и обо всем, что с ним связано
По всем вопросам и рекламе: @ajiekcx
Download Telegram
Представьте, что у вас есть список карточек, и на каждой из них отображается информация, которая обновляется в реальном времени.

Как правило, логикой обновления карточек занимается ViewModel экрана, однако это не всегда удобно. Что, если у каждой карточки будет собственный компонент логики, который будет подписываться на данные и самостоятельно изменять своё состояние?

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

🌳 Решить эту задачу можно с помощью библиотеки Decompose. Однако стандартные методы навигации из коробки не подходят. Наиболее близкий по смыслу — это ChildPages, который преимущественно используется в связке с ViewPager. Тем не менее, он отличается от ленивого списка.

Поэтому можно создать собственный тип навигации для использования с LazyList, который корректно управляет жизненным циклом компонентов:
🔵Видимые компоненты будут находиться в состоянии Resumed.
🔸Компоненты, ближайшие к видимым, — в состоянии Started.
🔸Остальные — в состоянии Created или Destroyed.

Таким образом, получилось декомпозировать логику и избавиться от бойлерплейта, связанного с отменой подписок.

Пример и реализацию кастомного механизма навигации можно посмотреть здесь 🐱
За решение спасибо моему коллеге Евгению Мельцайкину 👏
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥36👍6
Самая странная документация

Я, конечно, видел многое: когда у open-source библиотек документация отсутствовала совсем, была неполной или, наоборот, даже имела свой сайт с кучей классных примеров.

Но тут я столкнулся с gradle-плагином KMMBridge от известных ребят из Touchlab. Плагин предназначен для публикации KMP-фреймворка и его подключения через CocoaPods или SPM в iOS-проекте.

Итак, на GitHub никакой документации нет — всё на сайте, что, в целом, хорошо, идём туда. Прошерстив сайт, я понял, что это единственный плагин, в котором не написано, как его подключать. Как думаете, есть ли там информация о gradle-тасках, которые регистрирует плагин? Ответ очевиден 👎

Тут может возникнуть логичный вопрос: а что вообще есть?

Они предлагают создать GitHub-проект из шаблона, где всё за тебя настроят, включая GitHub Actions 🤡
Супер странное решение, как будто все будут стартовать проект с этого плагина. Ага, конечно.

В итоге пришлось разбираться в шаблоне и исходном коде плагина, чтобы понять, как его использовать. И всё равно пришлось делать форк, так как они не предусмотрели возможность менять endpoint при загрузке в S3-хранилище.

В целом, плагин довольно полезный: с ним можно удобно шарить общий код с iOS, находясь в разных репозиториях. Но такой подход к документации явно может отпугнуть большинство пользователей.

А от какой документации вы кринжанули больше всего?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍14👏5🙈32
Я снова принял участие в программном комитете конференции Podlodka Android Crew.

И нам нужна ваша помощь в выборе темы нового сезона. Какая из предложенных тем вам ближе, и вы бы хотели посетить сезон, посвящённый ей?

#️⃣Compose: 3 года в продакшене
#️⃣Тесты 360
#️⃣Карьера Android-разработчика

Заполните, пожалуйста, форму. Среди участников, заполнивших форму, мы разыграем проходку на конференцию.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18
Из раза в раз Android-разработчики страдают при обновлении зависимостей в поиске совместимых версий, и мы не стали исключением. Недавно нам понадобилось обновить версию библиотеки Coil для устранения проблем с бинарной совместимостью. Однако новая версия библиотеки подтянула за собой Kotlin 2.1.0, из-за чего наш Android Gradle Plugin начал выдавать множество предупреждений, связанных с R8, и предлагал заглянуть в таблицу совместимых версий. Стоит ли говорить, что в этой таблице новой версии Kotlin не оказалось 🫥

Ну что ж, обновляем AGP до последней версии в надежде, что всё заработает. Действительно, предупреждения исчезли, но минификация перестала работать 😬

Что же произошло
В версии AGP 8.4.2 изменили механизм работы R8.

Как было раньше
Сначала собирались все модули, затем выполнялась минификация каждого модуля

Как стало сейчас
Теперь минификация выполняется отдельно для каждого модуля сразу после его сборки. Из-за этого R8 считает, что код в модуле никем не используется, и просто удаляет его!

Как исправить
Правильным решением будет оставить флаг isMinifyEnabled только в application модуле. Поскольку он зависит от всех остальных модулей в приложении, минификация будет выполняться только после сборки всех модулей. Это обеспечит корректную работу минификации как для app-модуля, так и для остальных модулей.

Таким образом, оставив минификацию только для app-модуля, мы не только исправили проблему, но и ускорили сборку релизной версии, так как теперь минификация запускается только один раз.

#Android #Gradle #R8
Please open Telegram to view this post
VIEW IN TELEGRAM
👍47🫡53🔥2😁1
Мое первое мобильное приложение

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

Эта история об упорстве, ужасных костылях и успехе. Устраивайтесь поудобнее, запасайтесь фейспалмами и приятного чтения.
🔥39👍94
Media is too big
VIEW IN TELEGRAM
Хочу поделиться классным детективным подкастом, который сделали мои коллеги из Контура ☁️

Повествование ведётся от лица стажёра, который пришёл в команду базовой инфраструктуры, где случился один из самых серьёзных инцидентов в истории компании, изменивший многое.

В пяти эпизодах подкаста «Загадки хранителя зоопарка» не просто рассказывается об инциденте, а предлагается по-настоящему прожить тот самый день вместе с разработчиками.

На текущий момент вышло уже два эпизода, и послушать подкаст можно на всех популярных площадках.

Пишите в комментариях, как вам история и аудиоэффекты в подкасте 💬
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥131👍1👏1
Какая проблема есть в коде выше?

Пишите свои предположения в комментариях 💬
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
👍34
Decompose шаг за шагом

Если вы хотели попробовать Decompose, но не знали, с чего начать, то специально для вас Максим Казанцев выпустил подробный туториал по библиотеке. Шаг за шагом автор покажет, как создать простое приложение и познакомит вас со всеми основными компонентами Decompose.

Это лишь первая часть серии видео, в которых вы подробно узнаете, как использовать Decompose на разных платформах.

Смотреть на YouTube 🟥

Смотреть на VK Видео 📹

#Decompose #KMP
Please open Telegram to view this post
VIEW IN TELEGRAM
22👍10🤡2👏1
Decompose Detekt Rules

Я написал кастомные правила для Detekt, которые будут полезны в каждом проекте с Decompose.
На данный момент в библиотеке есть два правила:

🟣DecomposeComponentContextRule — проверяет, что вы не создаете ComponentContext внутри Composable функций, так как это может привести к падениям в рантайме.

🟣SerializableDiscriminatorRule — это правило не относится напрямую к Decompose, но тесно с ним связано. Оно проверяет, что в конфигурации компонента не указаны свойства, совпадающие с дискриминатором класса в kotlinx.serialization, подробнее про это здесь.

Правила работают с обычной конфигурацией Detekt, без type resolution. Также я не пытался охватить все возможные краевые кейсы, поэтому, если в вашем проекте они не работают, смело зводите issue.

🐱 Документация и инструкция по подключению находится здесь.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥231👍1
Сегодня мой коллега из Контура, Евгений Мельцайкин, будет проводить публичное собеседование Android-разработчика в том же формате, в котором мы проводим его в компании.

⌨️ На собеседовании будут проектировать мобильное приложение, продумывать архитектуру, писать код и отвечать на вопросы по Android-разработке.

🟥 Приходите посмотреть прямой эфир на YouTube-канале Android Broadcast сегодня в 19:00 мск.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👌2
☁️Оффлайн-встречи мобильных разработчиков уже в эти выходные!

😉Привет! На связи Coffee&Code — международное сообщество мобильных разработчиков.

😎Приглашаем вас на бесплатные встречи мобильных разработчиков в формате дружеской беседы. Будем обсуждать разработку, делиться опытом, задавать вопросы и просто приятно проводить время в кругу единомышленников.

🤪Пообщаемся на технические темы, обсудим интересные события из мобильной разработки, разберем вопросы с собеседований и поделимся опытом!

🤖 Android | 📱 Mobile | 🍏 iOS

📍СПИСОК ГОРОДОВ

💃Также мы выкладываем интересные технические/полезные видосики в наш YouTube канал и записываем Подкаст! Ждем тебя на встречах!
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥84👍1👨‍💻1👾1
Android Lint vs Detekt

Сегодня я провожу лекцию по статическому анализу и в связи с этим хочу сделать сравнение двух самых популярных инструментов для анализа вашего кода и не только. Несмотря на то, что Android Lint кажется чем-то устаревшим по сравнению с Detekt, на самом деле он имеет много преимуществ перед Detekt.

Android Lint
🟢Может анализировать не только Java/Kotlin код, но и XML, Gradle и TOML файлы
🟢Благодаря UAST можно писать универсальные правила для анализа Kotlin и Java кода
🟢Сразу же подсвечивает проблемы в IDE без необходимости запускать отдельную Gradle-таску
🟢Позволяет предоставлять исправления для проблемных участков кода
🟢Можно работать с Kotlin Analysis API для углубленного статического анализа
🔘Однако Android Lint работает только в Android-модулях
🔘Синтаксис написания правил, на мой взгляд, сложнее, чем в Detekt

Detekt
🟢Позволяет писать правила для KMP проектов
🟢Community-driven подход, есть множество open-source правил
🟢Работает с разными системами сборки
🟢Есть возможность использовать type resolution для анализа типов данных
🟢Работает с PSI, так же как и в IDE-плагинах
🔘Поддерживает только анализ Kotlin
🔘Без type resolution нельзя получить полное имя класса, в отличие от Android Lint
🔘Type resolution находится в experimental статусе, и мало кто использует эти проверки в проектах

📌 Таким образом, оба инструмента для статического анализа имеют свои сильные и слабые стороны, и не стоит сбрасывать Android Lint со счетов — возможно, он подойдет вам больше в некоторых случаях, чем Detekt.

#Detekt #AndroidLint #StaticAnalysis
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14🥱21👍1
🔥12🥴6🤯5😎3
Представим, что вы хотите реализовать список сессий конференции и разделить их по дате. Кажется, что реализация такой UI-модели будет довольно удачной идеей:


data class SessionsUiModel(
val sessionGroups: Map<String, List<Session>>
)


В Android это будет работать отлично, так как функция mapOf создает LinkedHashMap, который сохраняет порядок вставки элементов. И на самом деле все будет точно так же работать, если в iOS используется Compose Multiplatform. Однако если UI будет нативным на каждой платформе, то вы столкнетесь с проблемой.

При интеропе Kotlin-кода в Objective-C ваш Map превратится в NSDictionary (или Dictionary в Swift), который не гарантирует порядок вставки элементов.

Таким образом, не стоит полагаться на порядок элементов в Map, так как этот интерфейс не может гарантировать его. Предпочитайте использовать списки в UI-моделях, чтобы обеспечить одинаковое поведение на всех платформах:


data class SessionsUiModel(
val sessions: List<SessionItem>
)

sealed interface SessionItem {
data class Header(val header: String) : SessionItem
data class Item(val session: Session) : SessionItem
}


Если тема отличий в поведении между платформами в KMP интересна, то ставьте реакции и сделаю еще посты по теме.

#iOS #Android #KMP
👍85🔥11🤯31
Мы с вами говорили про отличия между платформами, но что говорить о мультиплатформе, если даже на разных Android-устройствах могут быть отличия в поведении, и явным рекордсменом по количеству особенностей являются устройства Xiaomi 🧡

Сегодня поговорим про регулярные выражения и букву ё. Кто ее только не отменял, но Xiaomi пошли дальше всех.

К нам прилетел баг, что при введении ФИО буква ё не проходит валидацию по регулярному выражению. Первое, что приходит в голову, это, что мы написали кривой regex, ведь на самом деле, если использовать такое регулярное выражение ^[а-яА-Я]*$ то буква ё не попадает в этот диапазон и нужно определять ее отдельно, но нет, дело было не в этом.

Дальше мы подумали, а что если клавиатура на Xiaomi использует какой-то другой символ ё и мы оказались правы. Действительно стандартная клавиатура использовала \u00eb символ юникода вместо \u0451

Все дело в клавиатуре, которая выбрана по умолчанию, если поменять клавиатуру на Gboard, то все будет окей. Так что перед проверкой данных нам пришлось делать замену данного символа, чтобы пройти валидацию и на бекенде 👍

Давайте порадуемся за Семëна с Xiaomi, теперь у него все будет хорошо 🫡

#Android #Regex #Xiaomi
Please open Telegram to view this post
VIEW IN TELEGRAM
🤡38😁31👍5🔥5😨2
Расскажу еще об одном интересном кейсе, который выстрелил у нас при работе с KMP. И здесь удар в спину пришел откуда не ждали — убийцей оказался SQLite 🔫

Мы используем библиотеку SQLDelight для работы с БД на Android и iOS, и в одном из приложений был реализован обычный поиск через оператор LIKE. По спецификации этот оператор является регистронезависимым для ASCII-символов, но не для символов Юникода. Например, символ æ не будет равен Æ.

Так вот, на Android все работает отлично — можно искать слова в разном регистре как на латинице, так и на кириллице. А на iOS для кириллицы регистр должен точно совпадать. При этом на iOS аналогично не работают для кириллицы другие SQL-фичи, вроде COLLATE NOCASE или функции LOWER. На этот счет в SQLDelight есть соответствующий issue.

Поэтому нам пришлось явно сохранять в БД информацию в нижнем регистре и приводить строку поиска к нижнему регистру, чтобы поиск работал корректно на обеих платформах.

Так что, если соберетесь делать локальный поиск в БД, помните об этом нюансе и не наступайте на наши грабли ❤️

#KMP #SQL #iOS
Please open Telegram to view this post
VIEW IN TELEGRAM
👍41🤔10❤‍🔥4🤯2
Розыгрыш билета на Podlodka Android Crew

Мы с командой в очередной раз подготовили для вас новый сезон конференции Podlodka Android Crew. В этот раз будем обсуждать Compose и разберём, что с ним произошло за три года в продакшене.

В программе будет много всего интересного:

⚙️ Доклад про опыт перехода на Compose Multiplatform
😤 Дебаты за звание лучшей навигации для Compose
😔 Собеседование по Compose с одним из участников конференции
Интересный квиз в формате «Своей игры»
🚀 И много всего другого

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

Но если вы не верите в силу рандома, то можете купить билет самостоятельно со скидкой 500 руб. по промокоду adept13
Please open Telegram to view this post
VIEW IN TELEGRAM
9🔥4
This media is not supported in your browser
VIEW IN TELEGRAM
Bottom Sheet починили! ⚪️

У нас в проектах уже давно нет зависимости от Material, так как используется своя дизайн-система. Это круто, но несёт одну главную проблему: ты перестаёшь следить за обновлениями в Material и исправлениями различных багов. До вчерашнего дня я был уверен, что Bottom Sheet в Material 3 всё ещё отвратительно работает и никакие проблемы там не исправили. Но оказалось, что это не так — все старые болячки там пофиксили 🤩

На видео можно заметить существенную разницу между старой и новой версией Bottom Sheet в Material 3.

P.S. Так что, видимо, надо начинать отдельный квест по копированию исходного кода этого компонента в нашу дизайн-систему 🙃
Please open Telegram to view this post
VIEW IN TELEGRAM
👍33😁194