Warning: Undefined array key 0 in /var/www/tgoop/function.php on line 65

Warning: Trying to access array offset on value of type null in /var/www/tgoop/function.php on line 65
135 - Telegram Web
Telegram Web
Мое первое мобильное приложение

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

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

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

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

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

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

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

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

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

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

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

#Decompose #KMP
Please open Telegram to view this post
VIEW IN TELEGRAM
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
Сегодня мой коллега из Контура, Евгений Мельцайкин, будет проводить публичное собеседование Android-разработчика в том же формате, в котором мы проводим его в компании.

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

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

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

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

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

🤖 Android | 📱 Mobile | 🍏 iOS

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

💃Также мы выкладываем интересные технические/полезные видосики в наш YouTube канал и записываем Подкаст! Ждем тебя на встречах!
Please open Telegram to view this post
VIEW IN TELEGRAM
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
Представим, что вы хотите реализовать список сессий конференции и разделить их по дате. Кажется, что реализация такой 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
Мы с вами говорили про отличия между платформами, но что говорить о мультиплатформе, если даже на разных Android-устройствах могут быть отличия в поведении, и явным рекордсменом по количеству особенностей являются устройства Xiaomi 🧡

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

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

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

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

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

#Android #Regex #Xiaomi
Please open Telegram to view this post
VIEW IN TELEGRAM
Расскажу еще об одном интересном кейсе, который выстрелил у нас при работе с 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
Розыгрыш билета на Podlodka Android Crew

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

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

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

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

Но если вы не верите в силу рандома, то можете купить билет самостоятельно со скидкой 500 руб. по промокоду adept13
Please open Telegram to view this post
VIEW IN TELEGRAM
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
Пока мы далеко не отошли от темы Bottom Sheet, хочу снова немного побомбить на то, какой API нам предоставили разработчики этого компонента в Material 3.

Я уже как-то поднимал тему декларативного Bottom Sheet, когда решение о том, показывать его или нет, определяется исключительно состоянием. То есть мы показываем шторку, если ассоциированный с ней стейт ≠ null, иначе скрываем.

И казалось бы, в Material 3 сделали именно так: достаточно просто установить значение false в переменной showBottomSheet, чтобы скрыть его. Но тогда это произойдет без анимации сворачивания компонента⚠️

Чтобы это исправить, придется явно вызывать suspend-функцию hide, но делать это каждый раз, мягко говоря, неудобно. Можно попробовать написать свою декларативную обертку, но придется решить несколько проблем:

🔘Как сохранять контент при анимации скрытия, если стейта уже нет?
🔘Как запретить перехватывать Bottom Sheet жестом, пока он сворачивается?

И вторая проблема самая неприятная, так как в Bottom Sheet нельзя отключить обработку жестов, пока он скрывается. Но нам обязательно нужно скрыть его, если ассоциированный стейт уже null, иначе получим неконсистентное состояние.

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

Обертку для Bottom Sheet из Material 3, которая отлично подходит для Slot навигации в Decompose, я уже реализовал и чуть позже поделюсь ею с вами, когда обновлю свой пример KMP-проекта.

#Compose #SwiftUI #BottomSheet
Please open Telegram to view this post
VIEW IN TELEGRAM
Обновил свой пример KMP-проекта SpaceXRockets, где показал как подружить Bottom Sheet из Material 3 с Decompose.

Это небольшой, но показательный пример, где можно увидеть:
🌳 Навигацию на Decompose для SwiftUI и Jetpack Compose
🎨 Интеграцию Compose Multiplatform в SwiftUI
⚙️ Работу с moko-resources
🐘 Реализацию многомодульности с изолированным DI в каждом модуле с помощью Koin

Звездочки на репозиторий приветствуются
Please open Telegram to view this post
VIEW IN TELEGRAM
Один из подписчиков канала, Алексей Илларионов, написал отличную статью про новый BundledSQLiteDriver из библиотеки androidx.sqlite.

В статье вы найдете:
🔘Преимущества и недостатки своей сборки SQLite
🔘Новые фичи BundledSQLiteDriver
🔘Ограничения, про часть из которых я писал ранее
🔘Замеры производительности

Приятного чтения 📕
Please open Telegram to view this post
VIEW IN TELEGRAM
Иногда при работе с TextField нам нужно управлять положением курсора — например, когда мы хотим отредактировать какой-то текст и вставить его в TextField. В таком случае курсор может остаться в начале строки. Для этого у TextField есть перегрузка, которая принимает TextFieldValue. Однако мы не можем использовать Compose-сущности в модулях без Compose.

Теперь посмотрите на код на изображении. Как думаете, в чём здесь проблема?

Колбэк onValueChange зациклится.

Но, несмотря на это, на большинстве устройств всё будет работать нормально: текст будет вводиться, и курсором можно будет управлять. Однако это не касается устройств Huawei — там текст будет вводиться через раз. Сначала я хотел поругать китайцев, но теперь хочу их похвалить: если бы не устройства Huawei, мы бы не отловили эту проблему.

Давайте разберёмся, почему так происходит:
1. При редактировании текста изменяются messageText и lastSelection.
2. Эти состояния объединяются, и изменяется messageTextField.
3. Устанавливается новое значение в TextField.
4. Снова вызывается onValueChange, поскольку TextFieldValue отличается. А отличается он из-за параметра composition, который неявно меняет сам Compose. Мы его не учитываем — из-за этого и происходит бесконечный цикл.

Как это исправить?
Самый логичный вариант — не использовать локальные состояния, а хранить TextFieldValue напрямую в стейте ViewModel. Но если такой возможности нет, можно создать аналогичный класс в бизнес-логике и маппить значения, главное учитывать все параметры TextFieldValue:


TextField(
value = messageState.toComposeTextFieldValue(),
onValueChange = { viewModel.onTextChanged(it.toDomainTextFieldValue()) }
)

#Compose
В этот раз посетил Mobius 😀 не в качестве спикера, хотя планировал сделать доклад о нашем опыте использования Compose Multiplatform в проде, но не случилось по личным обстоятельствам...

Уже по традиции после конференции планирую сделать обзор запомнившихся докладов и на самые интересные сделаю отдельные посты.

Если захотите пообщаться, то не стесняйтесь подходить, наверняка меня можно будет застать за фармом мерча на стендах 🤡
Please open Telegram to view this post
VIEW IN TELEGRAM
2025/07/04 07:51:34
Back to Top
HTML Embed Code: