Эту статью я начал писать ещё около года назад и вот наконец собрался и добил.
Хабр
Как «приручить» консоль, или 5 шагов к жизни с командной строкой
Всем привет! Меня зовут Осип, я Android-разработчик в red_mad_robot и я люблю автоматизировать всё, что автоматизируется. В этом мне помогает консоль, поэтому решил поделиться опытом, как настроить...
KSP (Kotlin Symbol Processing) вышел в альфу.
Это новый API для кодогенерации от Google на замену KAPT. Обещают, что он в два раза быстрее чем KAPT и гугловские либы с кодгеном будут переезжать на KSP.
Уже есть экспериментальная поддержка KSP для Moshi, но на этом пока что всё.
Подробнее про то, что это за покемон и как его пощупать читайте в официальном анонсе.
#kotlin #codegen
Это новый API для кодогенерации от Google на замену KAPT. Обещают, что он в два раза быстрее чем KAPT и гугловские либы с кодгеном будут переезжать на KSP.
Уже есть экспериментальная поддержка KSP для Moshi, но на этом пока что всё.
Подробнее про то, что это за покемон и как его пощупать читайте в официальном анонсе.
#kotlin #codegen
Если вы собираетесь мигрировать на Maven Central, вы наверняка знаете, что на создание репозитория нужно заводить issue в JIRA Sonatype. Так вот важная деталь - нужно создавать одну issue на верхнеуровневый пакет, а не для каждой либы.
Например, есть у вас либы с пакетами:
-
Так что всё не так страшно как мне казалось изначально и не нужно заводить 100 issue, если у вас 100 библиотек.
#publish
Например, есть у вас либы с пакетами:
-
my.domain.foo
- my.domain.bar
Нужно создать issue только для пакета my.domain и подтвердить, что вы владелец домена domain.my.Так что всё не так страшно как мне казалось изначально и не нужно заводить 100 issue, если у вас 100 библиотек.
#publish
Совет, который сохранил бы мне день. Будьте осторожны с конструкцией:
1. Подписка на
(У меня такое поведение воспроизвелось ещё и при восстановлении бэкстэка после перехода по диплинку)
В сумме получаем, что если у фрагмента вызывается
На практике я поймал это поведение при использовании viewbinding-ktx. Из-за этого получался неприятный баг при переходе по диплинку.
1. У промежуточного фрагмента вызывается
2. Вызывается
3. При следующем создании вьюхи фрагмента биндинг не пересоздаётся и мы используем биндинг со сылками на старые вьюхи.
4. Вместо нормального инициализированного экрана видим какую-то ерунду, скорее всего пустой экран.
Один из вариантов решения — использовать
В версии viewbinding-ktx 4.1.2-2 я исправил эту проблему немного по другому.
#bug #library #lifecycle
fragment.viewLifecycleOwnerLiveData.observe(fragment) { lifecycleOwner ->
lifecycleOwner.lifecycle.addObserver(...)
}
Две вещи про которые нужно помнить, чтобы понять в чём проблема:1. Подписка на
LiveData станет активной только после того как fragment перейдёт в состояние STARTED, то есть после метода onStart, т.к. используется LiveData.observe(LifecycleOwner)
2. onViewCreated может быть вызван без onStart, если вьюха создалась, но фрагмент не отобразился пользователю. Например, если вы переходите на следующий экран до того как текущий фрагмент успел отобразиться. (У меня такое поведение воспроизвелось ещё и при восстановлении бэкстэка после перехода по диплинку)
В сумме получаем, что если у фрагмента вызывается
onCreate - onViewCreated - onDestroyView, в подписке на viewLifecycleOwner мы об этом не узнаем. Если нужно чистить что-то при каждом уничтожении вьюхи фрагмента, это может быть критично.На практике я поймал это поведение при использовании viewbinding-ktx. Из-за этого получался неприятный баг при переходе по диплинку.
1. У промежуточного фрагмента вызывается
onViewCreated, в нём мы используем binding и он кэшируется.2. Вызывается
onDestroyView, но очистка биндинга не срабатывает потому что фрагмент ещё не перешёл в состояние STARTED и мы ещё не подписались на ЖЦ вьюхи3. При следующем создании вьюхи фрагмента биндинг не пересоздаётся и мы используем биндинг со сылками на старые вьюхи.
4. Вместо нормального инициализированного экрана видим какую-то ерунду, скорее всего пустой экран.
Один из вариантов решения — использовать
observeForever вместо observe, чтобы не привязываться к ЖЦ фрагмента.В версии viewbinding-ktx 4.1.2-2 я исправил эту проблему немного по другому.
#bug #library #lifecycle
Красивая картинка из доки вдогонку к прошлому посту про работу с ЖЦ. А заодно ещё одно наблюдение.
Если нужно во фрагменте выполнить suspend-функцию, которая влияет на UI, лучше использовать
Опять история про диплинки. Поймал случай, когда при открытии экрана по диплинку код в
Если знаете почему может быть такое поведение, напишите в комменты.
#lifecycle
Если нужно во фрагменте выполнить suspend-функцию, которая влияет на UI, лучше использовать
lifecycleScope.launchWhenStarted { ... }
а неlifecycleScope.launchWhenResumed { ... }
т.к. состояния STARTED уже достаточно, чтобы работать с UI.Опять история про диплинки. Поймал случай, когда при открытии экрана по диплинку код в
launchWhenResumed не выполняется т.к. фрагмент остаётся в состоянии CREATED и не переходит в состояние RESUMED. Пока не разобрался из-за чего так происходит, но похоже на баг, т.к. в норме onResume у фрагмента вызывается.Если знаете почему может быть такое поведение, напишите в комменты.
#lifecycle
Выпустил обновление 2.0-rc1 для mapmemory.
Эта библиотека позволяет быстро реализовать in-memory кэш.
Полностью release notes пересказывать не буду, но вот что хочется отметить:
- Теперь значения в MapMemory могут быть scoped и shared. Scoped ограничены к использованию только внутри класса где объявлены, shared можно шарить межу классами.
- Новый модуль
- Добавил поддержку RxJava 3
- Переписал README и KDoc комментарии. Жду комментов всё ли понятно теперь :)
- Упростил синтаксис для объявления nullable полей и полей со значением по умолчанию.
Гайд по миграции с v1.1 добавил в README.
#library
Эта библиотека позволяет быстро реализовать in-memory кэш.
Полностью release notes пересказывать не буду, но вот что хочется отметить:
- Теперь значения в MapMemory могут быть scoped и shared. Scoped ограничены к использованию только внутри класса где объявлены, shared можно шарить межу классами.
- Новый модуль
mapmemory-test с утилитами для удобного тестирования классов, использующих MapMemory.- Добавил поддержку RxJava 3
- Переписал README и KDoc комментарии. Жду комментов всё ли понятно теперь :)
- Упростил синтаксис для объявления nullable полей и полей со значением по умолчанию.
Гайд по миграции с v1.1 добавил в README.
#library
GitHub
Release v2.0-rc1 · RedMadRobot/mapmemory
Scoped and shared values (#1)
Now there are two types of memory values: scoped (to class) and shared.
All memory values by default are scoped to the class where it's declared.
Scoping prevents ...
Now there are two types of memory values: scoped (to class) and shared.
All memory values by default are scoped to the class where it's declared.
Scoping prevents ...
Что ж. Теперь точно пофиксили баг с утечкой фрагментов из Hilt
И в release notes к Dagger 2.34 он есть.
UPD: Пофиксили, но не доконца, а вот в 2.34.1 прям окончательно пофиксили. Честно-честно
И в release notes к Dagger 2.34 он есть.
UPD: Пофиксили, но не доконца, а вот в 2.34.1 прям окончательно пофиксили. Честно-честно
Telegram
Ra'Reilly
В Dagger 2.31.1 вошёл важный фикс Hilt, который почему-то не упомянули в Release Notes.
Вот он. Больше не будет утекать контекст фрагмента из-за Hilt.
UPD: Фикс ревертнули и он войдёт в 2.32
UPD2: Фикс так и не включили в 2.32, пока не понятно когда его…
Вот он. Больше не будет утекать контекст фрагмента из-за Hilt.
UPD: Фикс ревертнули и он войдёт в 2.32
UPD2: Фикс так и не включили в 2.32, пока не понятно когда его…
Пошёл на выходных обновить
Забавно в этом баге то как я пытался его воспроизвести, потому что он стрелял только в боевом проекте. Алгоритм воспроизведения довольно специфичный, поэтому в чистом проекте за несколько попыток воспроизвести не удалось.
Чтобы уж наверняка, скопировал весь рабочий проект и постепенно удалял из него всё лишнее, периодически пересобирая проект, чтобы убедиться, что баг не пропал. Получился около-бинарный поиск.
В итоге осталось три файлика и правильные настройки из которых смог слепить тестовый проект.
#bug #kotlin
mapmemory, но споткнулся о баг в котлине и не дошёл.Забавно в этом баге то как я пытался его воспроизвести, потому что он стрелял только в боевом проекте. Алгоритм воспроизведения довольно специфичный, поэтому в чистом проекте за несколько попыток воспроизвести не удалось.
Чтобы уж наверняка, скопировал весь рабочий проект и постепенно удалял из него всё лишнее, периодически пересобирая проект, чтобы убедиться, что баг не пропал. Получился около-бинарный поиск.
В итоге осталось три файлика и правильные настройки из которых смог слепить тестовый проект.
#bug #kotlin
YouTrack
KAPT: 'IllegalStateException: Couldn't find declaration file' on delegate with inline getValue operator : KT-46317
What steps will reproduce the issue? Environment: * Kotlin 1.4.32 (or 1.5.0-RC) * Gradle 6.8.3 * Java 11.0.10 Use the following code: import com.example.lib.Delegate // Condition 1. Use subclass from library class which has getValue operator class DelegateSubclass…
Если вы с одного устройства коммитите и в рабочий git-репозиторий, и в личный, наверняка были случаи, когда закоммитили код не с той почтой и именем.
И вот запушил что-то в рабочий проект с личного аккаунта, а потом сидишь и вычищаешь это через rebase перфекционизму ради.
Простой способ этого избежать — после того как стянул себе проект настроить всё что надо через
Более универсальный способ — использовать в глобальном
И вот запушил что-то в рабочий проект с личного аккаунта, а потом сидишь и вычищаешь это через rebase перфекционизму ради.
Простой способ этого избежать — после того как стянул себе проект настроить всё что надо через
git config. Если проектов много, а нужно настроить не только имя и email, но и ключ для подписи, например, это не удобно.Более универсальный способ — использовать в глобальном
.gitconfig настройку includeIf. Как можно понять из названия, эта настройка применит конфиги из другого файла, если выполняется условие. Синтаксис для определения условий достаточно ограниченный, но можно проверить в какой папке находится проект и в зависимости от этого применить настройки. У себя использую эту штуку так:[includeIf "gitdir:~/dev/rmr/"]В этом случае настройки из общего
path = .gitconfig.rmr
.gitconfig применяются ко всем проектам, а для проектов из папки dev/rmr/ некоторые настройки переопределяются в файле .gitconfig.rmr
#gitМожете считать, что меня только что разморозили, но я только вчера узнал про
Это альтернатива
submodule оставляет "метку", что "в этой папке должна лежать такая-то ревизия такого-то репозитория". Физически файлов из другого репозитория в вашем репозитории нет и чтобы они появились, нужно выполнить определённую команду. Наверное, это больше всего отпугивает от submodule — чтобы получить полностью рабочий репозиторий, недостаточно его клонировать, нужно ещё выполнить дополнительные действия.
subtree физически добавляет файлы из другого репозитория в ваш репозиторий вместе с историей коммитов. Процесс клонирования репозитория никак не меняется и другие разработчики могут не знать, что в проекте есть
Про
#git
git subtree.Это альтернатива
git submodule. Как и submodule, subtree позволяет подтянуть другой репозиторий внутрь вашего, но отличается подход.submodule оставляет "метку", что "в этой папке должна лежать такая-то ревизия такого-то репозитория". Физически файлов из другого репозитория в вашем репозитории нет и чтобы они появились, нужно выполнить определённую команду. Наверное, это больше всего отпугивает от submodule — чтобы получить полностью рабочий репозиторий, недостаточно его клонировать, нужно ещё выполнить дополнительные действия.
subtree физически добавляет файлы из другого репозитория в ваш репозиторий вместе с историей коммитов. Процесс клонирования репозитория никак не меняется и другие разработчики могут не знать, что в проекте есть
subtree. Файлы из "поддерева" можно изменять так же как с остальные файлы проектаПро
submodule должны знать все разработчики, а знать о subtree нужно только в момент когда хочется стянуть изменения из удалённого репозитория (pull) или наоборот запушить туда свои (push).# Добавляем удалённый репоизторий для subtreeP.S. Осталось научиться нормально синхронизировать файлы выборочно между репозиториями и будет совсем хорошо. Буду конфиги шарить направо и налево.
$ git remote add --fetch <repo_name> <url>
# Стягиваем его в нужную папку
$ git subtree add --prefix <dir> <repo_name> <ref>
# Ну и дальше можем делать fetch, pull, push
$ git subtree fetch --prefix <dir> <repo_name> <ref>
$ git subtree pull --prefix <dir> <repo_name> <ref>
$ git subtree push --prefix <dir> <repo_name> <ref>
#git
Сегодня убил кучу времени на баг которого не существует.
В апишке есть метод, который возвращает
→ Запрос улетает
← Приходит ответ с кодом 500 (видно по логам)
... Приложение чего-то ждёт и запрос отваливается по таймауту.
Что? Да. Запрос, ответ на который уже вроде как пришёл отваливается по таймауту.
Отгадка отказалась проста. Ответ 204 не подразумевает тела и в нём нет заголовка
Мораль — перед тем как чинить, убедитесь, что оно сломано :) А если серьёзно, то внимательнее смотрите на запросы и ответы которые тестировщики делают частичной подменой данных.
В апишке есть метод, который возвращает
204 No Content. Тестировщик подменил код ответа на 500, чтобы проверить как приложение реагирует на ошибку и в результате получилось такое поведение:→ Запрос улетает
← Приходит ответ с кодом 500 (видно по логам)
... Приложение чего-то ждёт и запрос отваливается по таймауту.
Что? Да. Запрос, ответ на который уже вроде как пришёл отваливается по таймауту.
Отгадка отказалась проста. Ответ 204 не подразумевает тела и в нём нет заголовка
Content-Length, а вот для 500 тело может прийти и Content-Length должен быть определён. В результате у HTTP-клиента ломается мозг и он до таймаута ждёт пока ему долетят остатки ответа, которых не существует.Мораль — перед тем как чинить, убедитесь, что оно сломано :) А если серьёзно, то внимательнее смотрите на запросы и ответы которые тестировщики делают частичной подменой данных.
Московские роботы-андроиды* выкатываются на YouTube!
Дебютное видео про всеми любимый Gradle.
Первые 50 минут теория, где:
- Разбираем какие могут возникнуть проблемы с Groovy у Java/Kotlin разработчика
- Смотрим как мигрировать с Groovy DSL на Kotlin DSL
- Кратко пробегаемся по основным APIшкам Gradle которые нужно знать, чтобы перейти ко второй части доклада.
Жанр второй части я бы назвал "косячный live coding", потому что это не тот live coding когда у докладчика всё отрепетировано и нет никаких проблем, а когда вы пробуете что-то написать самостоятельно, тут же огребаете. Тут собраны все косяки которые скорее всего соберёт человек, который впервые пишет плагины под Gradle.
Вторая часть длится полтора часа и в ней:
- Пишем Precompiled Script Plugin (после того как везде исправлял это название на монтаже я выучил как правильно!)
- Пишем плагин, с возможностью конфигурирования
- Пишем задачку (task). Делаем, чтобы в неё работало кэширование и создаём цепочки из задач
И это всё на близком к реальности Android-проекте.
Кстати, это один из докладов с нашей ежегодной робопрактики мобильных разработчиков.
Как говорится, подписывайтесь, ставьте лайки, рассказывайте друзьям :) Если понравится, будем продолжать.
* Мы в red_mad_robot не обижаемся, когда нас называют роботами, а даже наоборот сами себя так называем
#анонс #tooling
Дебютное видео про всеми любимый Gradle.
Первые 50 минут теория, где:
- Разбираем какие могут возникнуть проблемы с Groovy у Java/Kotlin разработчика
- Смотрим как мигрировать с Groovy DSL на Kotlin DSL
- Кратко пробегаемся по основным APIшкам Gradle которые нужно знать, чтобы перейти ко второй части доклада.
Жанр второй части я бы назвал "косячный live coding", потому что это не тот live coding когда у докладчика всё отрепетировано и нет никаких проблем, а когда вы пробуете что-то написать самостоятельно, тут же огребаете. Тут собраны все косяки которые скорее всего соберёт человек, который впервые пишет плагины под Gradle.
Вторая часть длится полтора часа и в ней:
- Пишем Precompiled Script Plugin (после того как везде исправлял это название на монтаже я выучил как правильно!)
- Пишем плагин, с возможностью конфигурирования
- Пишем задачку (task). Делаем, чтобы в неё работало кэширование и создаём цепочки из задач
И это всё на близком к реальности Android-проекте.
Кстати, это один из докладов с нашей ежегодной робопрактики мобильных разработчиков.
Как говорится, подписывайтесь, ставьте лайки, рассказывайте друзьям :) Если понравится, будем продолжать.
* Мы в red_mad_robot не обижаемся, когда нас называют роботами, а даже наоборот сами себя так называем
#анонс #tooling
YouTube
Gradle "без боли" / Почему Kotlin DSL? / Пишем плагины - Осип Фаткуллин
Разбираемся для чего нужен Gradle, чем же плох Groovy и почему мы в red_mad_robot MSK переехали на Kotlin DSL. Разбираем какие есть неочевидные APIшки в Gradle про которые точно нужно знать если собираетесь писать плагины. А ещё будет много практики!
Первый…
Первый…
Оказывается, плагин
Причём работает эта штука достаточно умно — она в зависимости от вашего "окружения" выбирает что подключать:
НО если вы руками объявили зависимость
Конечно, оставили флажок на случай если вы не хотите полагаться на эту логику, а хотите просто отключить автоматическое добавление
Благодаря этим строчкам версия из
Ещё один способ выбрать общую версию Kotlin для всего проекта.
#kotlin #gradle
kotlin-android (и другие kotlin-плагины) по умолчанию добавляют в зависимости stdlib. Такое поведение появилось начиная с версии 1.4.Причём работает эта штука достаточно умно — она в зависимости от вашего "окружения" выбирает что подключать:
stdlib-jdk8, stdlib-common или вообще stdlib-js.НО если вы руками объявили зависимость
stdlib, она уже не будет добавляться автоматически.Конечно, оставили флажок на случай если вы не хотите полагаться на эту логику, а хотите просто отключить автоматическое добавление
stdlib. В gradle.properties нужно добавить такую строчку:kotlin.stdlib.default.dependency=falseЕщё один интересный момент — версия для автоматически добавляемой зависимости берётся из свойства
kotlin.coreLibrariesVersion. В это свойство по умолчанию записывается версия подключённого kotlin-gradle-plugin, но её можно перезаписать.Благодаря этим строчкам версия из
kotlin.coreLibrariesVersion используется для всех зависимостей org.jetbrains.kotlin.Ещё один способ выбрать общую версию Kotlin для всего проекта.
#kotlin #gradle
YouTrack
Add stdlib by default in source-sets : KT-38221
Motivation 1. Currently, the absolute majority of Kotlin projects require dependency on stdlib and almost non-functional without it 2. For multiplatform projects, we already have an inconsistency with native stdlib (which is added by default) 3. The issue…
Всё. Теперь не работают отговорки типа "на Android не принято заголовок по центру тулбара делать", "это системное поведение, его нельзя поменять". Теперь можно.
#ui
#ui
Через неделю - 27-го июля с утра буду выступать на Podlodka Android Crew с рассказом про Git.
Планирую рассказать много всего - про чистоту истории, атомарные коммиты, разные модели ветвления, как всё-таки заставить всех разработчиков следовать договорённостям. Еще хочу обзорно рассмотреть полезные функции - cherry-pick, patch, stash и т.д. и как это всё ложится на Android Studio (читай IntelliJ IDEA).
Думаю, что доклад будет слабо связан с Android, поэтому может быть интересен не только андроид-разработчикам. Ссылка на запись доклада появится на этом канале :)
#анонс #git
Планирую рассказать много всего - про чистоту истории, атомарные коммиты, разные модели ветвления, как всё-таки заставить всех разработчиков следовать договорённостям. Еще хочу обзорно рассмотреть полезные функции - cherry-pick, patch, stash и т.д. и как это всё ложится на Android Studio (читай IntelliJ IDEA).
Думаю, что доклад будет слабо связан с Android, поэтому может быть интересен не только андроид-разработчикам. Ссылка на запись доклада появится на этом канале :)
#анонс #git
podlodka.io
Онлайн-конференция Podlodka Android Crew, сезон #15
Недельное мероприятие от команды Podlodka: ежедневные интерактивные сессии в Zoom по актуальным проблемам Android-разработки, нон-стоп общение с экспертами и звёздами индустрии, закрытое профессиональное сообщество в Telegram.
Чтобы было не скучно ждать пока мы зальём что-то ещё на канал, появился плейлист "Это тоже мы". Туда я складываю выступления с площадок вне нашего канала (пока что все, хе-хе).
Как и обещал, добавил в этот плейлист ссылку на свой доклад про Git с подлодки. Там доступ по ссылке, поэтому если вы не купили билет, то посмотреть можно пока только с нашего канала.
В общем, по классике - подписывайтесь, ставьте лайки, рассказывайте друзьям и вот это всё :)
#rmr #анонс
Как и обещал, добавил в этот плейлист ссылку на свой доклад про Git с подлодки. Там доступ по ссылке, поэтому если вы не купили билет, то посмотреть можно пока только с нашего канала.
В общем, по классике - подписывайтесь, ставьте лайки, рассказывайте друзьям и вот это всё :)
#rmr #анонс
Просматривал исходники AGP 7.0.0 и заметил, что теперь папка
Вот этот коммит.
В release notes AGP этого изменения не нашел.
#kotlin #gradle
kotlin по умолчанию добавлена в source-set и больше не нужно делать это вручную, если подключён Kotlin Gradle Plugin.Вот этот коммит.
В release notes AGP этого изменения не нашел.
#kotlin #gradle
На нашем канале пока тихо, зато Саша Серебренникова (@serebrennik) записала для @android_broadcast аж сразу серию видео про Custom View! Я уже посмотрел два из трёх видео и всем советую — Саша нереально крутая. Наконец материал с нормальными примерами, а не "нарисуем зелёный треугольничек с красным квадратиком".
Ссылочку на плейлист уже добавил на канал, чтобы все наши выступления можно было найти в одном месте :)
#ui #анонс
Ссылочку на плейлист уже добавил на канал, чтобы все наши выступления можно было найти в одном месте :)
#ui #анонс