Forwarded from Mobius — канал конференции
#видеозаписи
Когда начинаешь публиковать записи докладов Mobius, с какой платформы логичнее приступать, Android или iOS?
С обеих сразу: в этот #МобильныйВторник открываем доклад, где речь про обе.
YouTube | VK Видео
Скачать презентацию с сайта Mobius
Когда начинаешь публиковать записи докладов Mobius, с какой платформы логичнее приступать, Android или iOS?
С обеих сразу: в этот #МобильныйВторник открываем доклад, где речь про обе.
YouTube | VK Видео
Скачать презентацию с сайта Mobius
👍19❤1
Compose Multiplatform в проде
Хочу поделиться новостью: мы выпустили первое приложение, полностью написанное на Compose Multiplatform для iOS😌
Изначально приложение разрабатывалось только для Android, но использовался Kotlin-стек (Decompose, Ktor, SqlDelight, Koin) и обычный Jetpack Compose. Чтобы запустить его в каком-то виде на iOS, потребовалось всего 4 дня! Конечно, доведение до релиза заняло значительно больше времени, но всё равно это оказалось гораздо быстрее, чем полноценная разработка аналогичного проекта с нуля.
Что по итогам:
🟣 Compose в релизной версии вполне прилично работает, особенно на новых устройствах с поддержкой 120 Гц
🟣 Управление жестами удалось легко реализовать благодаря Decompose
🟣 Скролл подлагивает и не ощущается как нативный
🟣 BottomSheet, как всегда причиняет боль 😬
🟣 Есть некоторые баги с TextField
🟣 Некоторые контролы пришлось реализовать нативно, например, WebView, TimePicker и т.д.
Тем не менее, я уверен, что многие проблемы будут исправлены в будущем и уже сейчас Compose Multiplatform можно использовать в проектах, где плавность интерфейса не является критически важной👍
#iOS #Compose
Хочу поделиться новостью: мы выпустили первое приложение, полностью написанное на Compose Multiplatform для iOS
Изначально приложение разрабатывалось только для Android, но использовался Kotlin-стек (Decompose, Ktor, SqlDelight, Koin) и обычный Jetpack Compose. Чтобы запустить его в каком-то виде на iOS, потребовалось всего 4 дня! Конечно, доведение до релиза заняло значительно больше времени, но всё равно это оказалось гораздо быстрее, чем полноценная разработка аналогичного проекта с нуля.
Что по итогам:
Тем не менее, я уверен, что многие проблемы будут исправлены в будущем и уже сейчас Compose Multiplatform можно использовать в проектах, где плавность интерфейса не является критически важной
#iOS #Compose
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥55👍5👏2🤔2❤1
Как подружить Web History и Compose resources
Недавно столкнулся с проблемой: после добавления поддержки Web History в проект с Compose для Web у меня перестали работать ресурсы, причём это происходило только на вложенных экранах.
Изначально я предположил, что проблема связана с настройками веб-сервера, но нет. В Compose для Web ресурсы загружаются по относительному пути. Это означает, что к текущему URL в браузере добавляется путь до ресурсов. Соответственно, если вы находитесь не на главной странице, то по такому пути ресурсы окажутся недоступными🫥
Посмотрел, что пишут в документации, но никаких рекомендаций там не дается на этот счет, что довольно странно. Поправить же проблему удалось следующим образом:
Добавляем этот код в функцию main в сорсете jsMain, и пути до ресурсов снова становятся корректными.
#Compose #JS #WEB
Недавно столкнулся с проблемой: после добавления поддержки Web History в проект с Compose для Web у меня перестали работать ресурсы, причём это происходило только на вложенных экранах.
Изначально я предположил, что проблема связана с настройками веб-сервера, но нет. В Compose для Web ресурсы загружаются по относительному пути. Это означает, что к текущему URL в браузере добавляется путь до ресурсов. Соответственно, если вы находитесь не на главной странице, то по такому пути ресурсы окажутся недоступными
Посмотрел, что пишут в документации, но никаких рекомендаций там не дается на этот счет, что довольно странно. Поправить же проблему удалось следующим образом:
configureWebResources {
resourcePathMapping { path -> "${location.origin}/$path" }
}
Добавляем этот код в функцию main в сорсете jsMain, и пути до ресурсов снова становятся корректными.
#Compose #JS #WEB
Please open Telegram to view this post
VIEW IN TELEGRAM
👍22😨4
Интероп 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
👍15😁1
Давайте тоже подведем итоги года. За этот год канал невероятно вырос по количеству подписчиков. Спасибо, что читаете, ставите лайки и делитесь постами — для меня это важно ❤️
Судя по количеству постов, очевидно, что я не самый продуктивный автор, но стараюсь делать уникальный и, надеюсь, полезный контент. Если у вас есть пожелания, какие посты вы хотели бы видеть чаще в следующем году, смело пишите в комментариях💬
И всех с наступающим Новым годом🚗
Судя по количеству постов, очевидно, что я не самый продуктивный автор, но стараюсь делать уникальный и, надеюсь, полезный контент. Если у вас есть пожелания, какие посты вы хотели бы видеть чаще в следующем году, смело пишите в комментариях
И всех с наступающим Новым годом
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥30🎄11❤4👍1💯1
Представьте, что у вас есть список карточек, и на каждой из них отображается информация, которая обновляется в реальном времени.
Как правило, логикой обновления карточек занимается ViewModel экрана, однако это не всегда удобно. Что, если у каждой карточки будет собственный компонент логики, который будет подписываться на данные и самостоятельно изменять своё состояние?
Проблема здесь в том, что необходимо отменять подписку и освобождать ресурсы, если карточка больше не видна на экране.
🌳 Решить эту задачу можно с помощью библиотеки Decompose. Однако стандартные методы навигации из коробки не подходят. Наиболее близкий по смыслу — это ChildPages, который преимущественно используется в связке с ViewPager. Тем не менее, он отличается от ленивого списка.
Поэтому можно создать собственный тип навигации для использования с LazyList, который корректно управляет жизненным циклом компонентов:
🔵 Видимые компоненты будут находиться в состоянии Resumed.
🔸 Компоненты, ближайшие к видимым, — в состоянии Started.
🔸 Остальные — в состоянии Created или Destroyed.
Таким образом, получилось декомпозировать логику и избавиться от бойлерплейта, связанного с отменой подписок.
Пример и реализацию кастомного механизма навигации можно посмотреть здесь🐱
За решение спасибо моему коллеге Евгению Мельцайкину👏
Как правило, логикой обновления карточек занимается ViewModel экрана, однако это не всегда удобно. Что, если у каждой карточки будет собственный компонент логики, который будет подписываться на данные и самостоятельно изменять своё состояние?
Проблема здесь в том, что необходимо отменять подписку и освобождать ресурсы, если карточка больше не видна на экране.
Поэтому можно создать собственный тип навигации для использования с LazyList, который корректно управляет жизненным циклом компонентов:
Таким образом, получилось декомпозировать логику и избавиться от бойлерплейта, связанного с отменой подписок.
Пример и реализацию кастомного механизма навигации можно посмотреть здесь
За решение спасибо моему коллеге Евгению Мельцайкину
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, находясь в разных репозиториях. Но такой подход к документации явно может отпугнуть большинство пользователей.
А от какой документации вы кринжанули больше всего?
Я, конечно, видел многое: когда у 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🙈3✍2
Я снова принял участие в программном комитете конференции 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
Ну что ж, обновляем 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🫡5✍3🔥2😁1
Мое первое мобильное приложение
На днях вспомнил, как несколько лет назад я вел коллективный твиттер-аккаунт мобильного разработчика, где рассказывал историю создания моего первого мобильного приложения, и теперь решил поделиться этой историей с вами.
Эта история об упорстве, ужасных костылях и успехе. Устраивайтесь поудобнее, запасайтесь фейспалмами и приятного чтения.
На днях вспомнил, как несколько лет назад я вел коллективный твиттер-аккаунт мобильного разработчика, где рассказывал историю создания моего первого мобильного приложения, и теперь решил поделиться этой историей с вами.
Эта история об упорстве, ужасных костылях и успехе. Устраивайтесь поудобнее, запасайтесь фейспалмами и приятного чтения.
🔥39👍9❤4
Media is too big
VIEW IN TELEGRAM
Хочу поделиться классным детективным подкастом, который сделали мои коллеги из Контура ☁️
Повествование ведётся от лица стажёра, который пришёл в команду базовой инфраструктуры, где случился один из самых серьёзных инцидентов в истории компании, изменивший многое.
В пяти эпизодах подкаста «Загадки хранителя зоопарка» не просто рассказывается об инциденте, а предлагается по-настоящему прожить тот самый день вместе с разработчиками.
На текущий момент вышло уже два эпизода, и послушать подкаст можно на всех популярных площадках.
Пишите в комментариях, как вам история и аудиоэффекты в подкасте💬
Повествование ведётся от лица стажёра, который пришёл в команду базовой инфраструктуры, где случился один из самых серьёзных инцидентов в истории компании, изменивший многое.
В пяти эпизодах подкаста «Загадки хранителя зоопарка» не просто рассказывается об инциденте, а предлагается по-настоящему прожить тот самый день вместе с разработчиками.
На текущий момент вышло уже два эпизода, и послушать подкаст можно на всех популярных площадках.
Пишите в комментариях, как вам история и аудиоэффекты в подкасте
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥13❤1👍1👏1
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
👍34
Decompose шаг за шагом
Если вы хотели попробовать Decompose, но не знали, с чего начать, то специально для вас Максим Казанцев выпустил подробный туториал по библиотеке. Шаг за шагом автор покажет, как создать простое приложение и познакомит вас со всеми основными компонентами Decompose.
Это лишь первая часть серии видео, в которых вы подробно узнаете, как использовать Decompose на разных платформах.
Смотреть на YouTube🟥
Смотреть на VK Видео📹
#Decompose #KMP
Если вы хотели попробовать 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.
🐱 Документация и инструкция по подключению находится здесь.
Я написал кастомные правила для Detekt, которые будут полезны в каждом проекте с Decompose.
На данный момент в библиотеке есть два правила:
Правила работают с обычной конфигурацией Detekt, без type resolution. Также я не пытался охватить все возможные краевые кейсы, поэтому, если в вашем проекте они не работают, смело зводите issue.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥23❤1👍1
Сегодня мой коллега из Контура, Евгений Мельцайкин, будет проводить публичное собеседование Android-разработчика в том же формате, в котором мы проводим его в компании.
⌨️ На собеседовании будут проектировать мобильное приложение, продумывать архитектуру, писать код и отвечать на вопросы по Android-разработке.
🟥 Приходите посмотреть прямой эфир на YouTube-канале Android Broadcast сегодня в 19:00 мск.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👌2
🤖 Android | 📱 Mobile | 🍏 iOS
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8❤4👍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
Сегодня я провожу лекцию по статическому анализу и в связи с этим хочу сделать сравнение двух самых популярных инструментов для анализа вашего кода и не только. Несмотря на то, что Android Lint кажется чем-то устаревшим по сравнению с Detekt, на самом деле он имеет много преимуществ перед Detekt.
Android Lint
Detekt
#Detekt #AndroidLint #StaticAnalysis
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥14🥱2❤1👍1
Будет ли сохранен порядок вставки элементов при использовании функции mapOf() в Kotlin?
Anonymous Quiz
33%
Порядок элементов будет сохранен
51%
Порядок элементов не гарантируется
8%
Будет отличаться в зависимости от платформы
8%
Будет отсортирован по ключу
🔥12🥴6🤯5😎3