Ra'Reilly - Заметки про Ktor и не только
971 subscribers
71 photos
134 links
Раз в никогда тут появляются заметки.
В основном про около-Ktor, но иногда и про тулинг залетает.

Автор: @osipxd
Download Telegram
У нас в проекте используются гугловские апишки, многие из которых возвращают Task и чтобы превратить его в корутину, используем такой экстеншен.

А сегодня пока обновлял каталоги версий наткнулся на kotlinx-coroutines-play-services. И там помимо await есть ещё конвертация в Deferred и обратно.

#kotlin
👍6💩1
Коротенькая статья откуда в Unix системах тянется вся эта борода с директориями /bin, /sbin, /usr/bin, /usr/local/bin и т.д.
Если интересно для чего нужны остальные каталоги в корне, можно почитать мануал который открывается по команде man hier (от слова hierarchy).

Есть ещё много подобных похожих, но при этом разных, вещей. Например разница между .bash_profile и .bashrc:
- .bash_profile исполняется при запуске "login shell". Например, при автризации через SSH или в tty
- .bashrc - в остальных случаях при запуске интерактивной оболочки. Например, при открытии терминала или при запуске из другой оболочки по команде bash

Казалось бы зачем эта информация, но это полезно знать чтобы понимать, почему переменные окружения, добавленные в .bash_profile не работают, а в .bashrc работают.
👍1🔥1
Недавно вышел compose-compiler 1.2.0, а значит пора пересаживать проекты на Kotlin 1.7.0.
Причём не обязательно переводить весь проект на compose 1.2.0-rc03, достаточно обновить только compiler.
Судя по исходному коду compose-compiler 1.2.0 может компилировать и старые версии начиная с compose 1.0.0

#kotlin #compose
В ноябре записался в ранний доступ к Fleet и вот сейчас, когда я про это уже забыл, пришло письмо, что можно его установить и опробовать :)

Запускается и работает быстро, умные функции типа автодополнения и инспекций включает только когда они нужны - если нажать специальную кнопочку. Содержимое панелей слева снизу и справа можно настроить под себя.
В общем, Fleet идеально справляется с задачей текстового редактора с подсветкой синтаксиса..

Полноценно заменить IDEA или AS для больших проектов Flet пока не может. Скорее всего вам будет не хватать Android плагина и других интеграций с используемыми фреймворками.

Мои мечты, что можно будет открыть любой скрипт на python или bash и запустить его одной кнопочкой, к сожалению, пока не сбылись. Во-первых Fleet не поддерживает shebang, во вторых, питоновский скрипт без приседаний запустить не удалось из-за бага. Уверен, эти проблемы поправят в будущем.

На очереди ещё затестить главные фичи:
- возможность подключиться в режиме "териминала" к Fleet'у на удалённой машинке
- режим совместной работы - интересно отличается ли он от Code With Me
#tools #fleet
👍3
При работе с Jira, как и с любой системой учёта задач, есть одна проблема — как только количество задач переваливает за условную 1000, поддерживать порядок становится сложно.
Частично эту проблему можно решить настроив автоматизации и флоу так, чтобы завести задачу в неправильное состояние было бы просто невозможно, но при этом всё равно хочется убедиться, что ничего не потерялось, т.к. любая автоматика может дать сбой.

На нашем проекте пытаюсь решить эту проблему с помощью фильтров. И вот этот фильтр, внезапно, оказался очень полезным. Мы сразу выловили 15 задач застывших в неправильном статусе.

project = PRJ AND component = Android AND ((statusCategory = "In Progress" AND (assignee is EMPTY OR updated <= "-5d")) OR (statusCategory = "To Do" AND assignee is not EMPTY AND updated <= "-7d") OR (status = Test AND fixVersion is EMPTY))

#jira #snippet
👍3
Как вам новая цветовая схема "New Dark" в IDEA?

#idea
👍6👎2🤔2
Чем больше узнаю про всяческие оптимизации которые делает компилятор и инструменты типа R8, тем меньше верю в код который пишу.

Был в нашем проекте такой код с рефлексией:
SnackbarHostState::class.java
.declaredFields
.first { it.type == Mutex::class.java }
Я был уверен, что этот код достаточно безопасный и устойчивый к обфускации, т.к. мы не завязываемся на имя поля, а находим его по типу. Поле с типом Mutex в SnackbarHostState единственное, что может пойти не так?
Но всё сломалось после очередного обновления зависимостей и Android Gradle Plugin. Релизные сборки начали падать с ошибкой NoSuchElementException: Array contains no element matching the predicate в месте вызова first.

Первым делом я проверил, что реализация класса не поменялась и поле mutex не пропало — оно на месте, да и если бы оно пропало, падали бы не только релизные сборки.
Тогда заглянул в реализацию функции Mutex() чтобы удостовериться, что возвращаемый тип по прежнему Mutex — тоже всё в порядке.
И вот тут закралось подозрение, что R8 просто заинлайнил вызов функции и напрямую подставил вызов конструктора, сломав тип поля при этом.

Просмотр dex файла показал, что так и есть — у поля mutex теперь тип MutexImpl (который, кстати, internal). Не, ну а почему бы и нет? Сайд-эффектов у Mutex() нет, поле mutex приватное, от изменения типа публичное API не пострадает, так что почему бы не заинлайнить?

Полечил проблему поменяв код внутри first так, чтобы находить любой тип, реализующий интерфейс Mutex:
Mutex::class.java.isAssignableFrom(it.type)

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

#r8
👍9
Если загуглить "как переподписать APK", скорее всего наткнётесь на советы типа "удалить из APK папку META-INF, прогнать zipalign и подписать APK заново".
Но если так сделать с Signature Scheme v2, будут проблемки.

При подписи через jarsigner (v1) все изменения действительно вносятся только внутрь META-INF. Достаточно удалить эту папку, чтобы получить свеженький, нетронутый подписями APK. Но в v2 к APK добавляется APK Signing Block, который так просто уже не удалишь.

На самом деле решение простое — вообще ничего не делать. apksigner теперь сам умеет переподписывать APK новой подписью.
А ещё можно воспользоваться моим скриптом (чтобы получить красоту как на скриншоте). Он уже учитывает всяческие тонкости, а заодно умеет подписывать AAB.

#snippet #signing
👍7
Сегодня в исходниках Retrofit наткнулся на доку к методу baseUrl. И вот казалось бы давно использую Retrofit, а прочитал её первый раз.
То есть я и до этого замечал закономерность, что если путь эндпоинта начинается со слеша, то он используется как абсолютный, а если слеша нет — как относительный, но доходил до этого через долгое тупление в код и просмотр запросов в сниффере. Ответ же оказывается всё это время был тут, рядом. Более того на эту доку есть ссылки из описания каждой аннотации-HTTP-метода, так что нельзя сказать, что документация расположена в неожиданном месте и её сложно найти.

Там же в доке есть описание как работает сериализация параметров, которые передаются с аннотациями @Path, @Query и т.д. По умолчанию просто вызывается .toString() и этого в большинстве случаев достаточно, но для сериализации каких-нибудь дат и времени будет полезно определить stringConverter в фабрике конвертеров.

Помню, что несколько раз заходил на "сайт" ретофита, но не находил там документации и шёл гуглить вместо того чтобы посмотреть исходники. При этом забавно, что я сам придерживаюсь точки зрения, что лучшее место для документации — как можно ближе к коду, в комментариях к классам и методам. Теперь сижу и думаю, точно ли это так? Я единственный так невнимательно отнёсся к доке Retrofit?
Давайте проверим. Если вы как и я пропустили эти моменты в доке Retrofit, ставьте 🤔
🤔10😁3
Вчера днём, когда я начинал писать про группировку ошибок в Crashlytics, я не представлял, что это выльется в такой длиннопост из костылей и велосипедов (5 минут на прочтение).
Надесь на обратную связь. 😉 Стоит ли продолжать тему и рассматривать альтернативный подход?
👍5🔥2
Всегда было интересно как реализованы динамические иконки календаря и часов. Иконка календаря показывает актуальную дату, а стрелки на иконке часов совпадают с реальным временем.
Ну то есть понятно, что это реализовано со стороны лаунчера, но интересно можно ли как-то сделать такое же для своего приложения... конечно же нельзя.
Судя по коду динамические иконки это просто хардкод для календаря и часов. При этом для этих иконок используются разные подходы:

🗓 Для календаря в loadCalendarDrawable лаунчер получает номер дня недели и загружает для него соответствующий drawable. Сами drawable берутся из Google-календаря.
🕦 Для часов же лаунчер отрисовывает иконку в ClockDrawableWrapper. При этом судя по коду для иконки даже предусмотрена возможность отображения секундной стрелки (отключена в коде) и есть анимация при смене положения стрелок.

Кстати, там же, в ThemedIconDrawable можно посмотреть как устроена отрисовка монохромных иконок с цветом темы.
👍9
Знаю, что среди подписчиков теперь есть дизайнеры. Не обещаю, что будет интересно, но иногда буду писать посты на стыке дизайна и разработки.

Сегодня хочу написать об ошибке вёрстки, которую встречаю на ревью чаще остальных👇
Когда я только начинал использовать Material, я заметил, что кнопки в вёрстке располагаются неправильно. Вроде отступы выставляю как в дизайне, но они почему-то получаются больше. Оказалось у кнопки есть дополнительные отступы по 6dp снизу и сверху. Что я сделал вместо того чтобы вникать для чего эти отступы? Правильно — загуглил "How to remove padding around button". На SO никто тоже не задавался вопросом зачем эти отступы, поэтому я был уверен, что убрать отступы это единственное правильное решение. Но ведь вряд ли их добавили, только чтобы поломать вёрстку?

Возможно, вы уже догадались по прикреплённой картинке, во всём виноваты гайдлайны accessibility. Чтобы интерфейсом было удобно пользоваться всем, минимальная область нажатия (touch target) должна быть 48dp по ширине и по высоте. Стандартная material кнопка имеет высоту 36dp, поэтому чтобы добить её до 48dp снизу и сверху добавляются отступы по 6dp, которые входят в область нажатия.
В XML за эти отступы отвечают атрибуты insetTop и insetBottom, а в compose MinimumTouchTargetModifier.

В случае с обычными кнопками об аccessibility за нас позаботились разработчики Google. Всё гораздо хуже, когда появляются кнопки-иконки и текстовые кнопки.
Разработчик видит в дизайне иконку размером 24dp и просто добавляет иконку и вешает на неё возможность клика. С точки зрения вёрстки всё идеально, но вот нажимать кнопку с областью нажатия 24dp не слишком удобно. С текстовыми кнопками та же история. Если в дизайне текстовая кнопка выглядит как обычный текст, скорее всего разработчик так её и реализует — область нажатия такой кнопки по высоте будет и того меньше.

Разработчик, который помнит о минимальной области нажатия, обрекает себя на вечные упражнения по арифметике за 3-й класс. Добавление кнопки-иконки будет выглядеть примерно так: "Хм, у этой иконки размер 24, но надо добить его до 48 с помощью отступов. Это получается дополнительно по 12 с каждой стороны. А какие отступы у компонента в котором эта иконка? Ага, слева от края до иконки 16, а сверху 12. Значит вычитаем из этих значений 12, которые добавляем внутрь иконки и делаем отступ слева 4, а сверху 0."
И это ещё благоприятная ситуация, когда вокруг иконки хватает внешних отступов, чтобы компенсировать добавленные внутренние. А потом придёт другой разработчик, который не поймёт почему тут отступы отличаются от дизайна и приведёт их в соответствие дизайну. Вёрстка опять поехала :)

Что можно сделать:
👉 Для кнопок-иконок используйте IconButton, вместо Icon.
Если XML: сделайте отдельный стиль, в этом issue есть множество вариантов.
👉 Для текстовых кнопок используйте TextButton, вместо Text.
Если XML: кнопка со стилем Widget.MaterialComponents.Button.TextButton — наш выбор
👉 И самое главное — просите дизайнеров учитывать области нажатия компонентов в макетах. В идеале включить дополнительные отступы в кнопки и создать компоненты (символы) для текстовых кнопок и кнопок-иконок. Расскажите слёзную историю как неудобно для каждого элемента считать отступы в уме. Надеюсь удастся их убедить.

Пусть наши интерфейсы будут не только красивыми, но и удобными!

#ui #pains
👍74
Сегодня поста не будет, извините
Берегите себя.
🙏18💩1
В последнее время на работе часто рисую диаграммы для документации.
Сегодня расскажу какие инструменты для этого использую.

Для контекста — у нас большая часть документации пишется в Markdown файлах прямо в репозитории проекта. Отсюда вытекает требование, что хочется иметь возможность быстро редактировать диаграммы. Желательно чтобы для редактирования не нужно было ставить дополнительный софт.
Ещё не всегда хочется заморачиваться с красивым расположением элементов на схеме, лучше чтобы оно как-то само расположилось.

На помощь приходит Mermaid. Он позволяет описывать диаграмму текстом, а отрисовку берёт на себя.
Например, чтобы сгенерировать диаграмму для картинки сверху, понадобилось 4 строчки:

flowchart LR
start([Начало]) --> question{Пришла \n повестка?}
question -- Да --> trash[Выкинуть] --> endnode
question -- Нет --> endnode([Конец])

Само по себе то что диаграмму можно "запрогать" вместо того чтобы рисовать руками, это уже круто. Ещё круче, что в большинстве случаев вам не понадобится сохранять результат в виде картинки, потому что рендеринг Mermaid поддерживается в GitHub и GitLab. Всё что нужно, чтобы внести изменения в такую диаграмму — изменить код, описывающий её. Кстати, официальный плагин Markdown для IDEA умеет рендерить Mermaid.

Mermaid на данный момент поддерживает 11 видов диаграмм, среди которых помимо очевидных sequence- и class-диаграмм есть ещё диаграммы графа гита. Если этого недостаточно, посмотрите в сторону PlantUML. В нём больше видов диаграмм и возможностей для кастомизации. Синтаксис там посложнее и нет поддержки в GitHub, но всё ещё есть поддержка в GitLab и в IDE.

Иногда есть желание и вовсе затащить диаграмму в описание функции или класса. В этом случае можно использовать ASCII диаграммы. Те самые, которые вы наверняка не раз встречали во всяческих RFC (кстати вот вам RFC про ASCII Art :))
Для простеньких ASCII диаграмм можно использовать asciiflow. Есть ещё Diagon, он может в ASCII и красивую табличку отрендерить, и иерархию каталогов, и EBNF, и ещё много чего.

#tooling
👍6
Прошёл почти год с прошлого поста момента когда я написал "временную" либу для шифрования DataStore и два года с создания feature request в трекере 🎉
Раз мы всё ещё не увидели официального решения для шифрования от Google, я решил привести либу в приличный вид.

Что нового
1️⃣ Интеграция с security-crypto. Благодаря этой интеграции стало сложнее использовать либу неправильно. Больше не нужно получать Aead через Tink, вместо этого достаточно передать EncryptedFile при создании DataStore.
2️⃣ Шифрование в потоке. Раньше это было не так, файл для (де)шифровки считывался в ByteArray целиком.
3️⃣ Больше документации. Теперь помимо README, примеры использования методов есть в комментариях к самим методам.

⚠️ Чтобы при переезде на новую версию не потерять данные, посмотрите гайд по миграции.

* На скриншоте пример использования новой либы. Если использовать security-crypto-ktx:1.1.0, уйдут билдеры и код станет ещё проще

#datastore #security
🔥134
Про DataStore

Google представляет DataStore как замену SharedPreferences, а ещё часто называет его не иначе как Proto DataStore, подчёркивая что данные хранятся в формате protobuf. Такое позиционирование сбивает с толку.

1️⃣ Это не "замена SharedPreferences"

По крайней мере не прямая замена.
API для работы с Preferences DataStore сильно отличается от префов и приходится "обмазываться" экстеншенами, чтобы использовать его было хоть сколько-нибудь удобно. После первой попытки использования, "зелёные галочки" у DataStore в табличке сравнения не кажутся такими уж важными и чаще появляется мысль "нормально же жили, зачем что-то выдумывать". Вообще создаётся впечатление, что Preference DataStore сделали только для того чтобы переманить разработчиков с SharedPreferences и вышло не очень.

Первое что нужно сделать — выкинуть Preferences DataStore и хранить данные не формате "ключ - значение", а в нормальном классе с типизацией. Это может выглядеть так:

data class AppPreferences(
val onboardingShown: Boolean,
val forceDarkTheme: Boolean,
)

Есть плюсы и помимо типизации. Можно хранить не только примитивы и списки, но и любые типы данных которые можно сериализовать.

2️⃣ Protobuf не обязателен

И даже будет минусом, если:
- до этого с ним не работали
- не хочется (или нельзя) тащить новую зависимость в проект

Несмотря на то, что Google везде упоминает protobuf, его использовать не обязательно. Формат сериализации данных полностью контролируется сериализатором, который вы передаёте при создании DataStore. У меня для вас даже есть готовая функция для создания сериализатора в формате Json на основе kotlinx.serialization.
☝️Если всё-таки хочется оставить protobuf, посмотрите в сторону поддержки protobuf в kotlinx.serialization, возможно окажется удобнее чем официальная либа гугла.

ℹ️ TL;DR

Чтобы решить для себя нужен вам DataStore или нет, предлагаю рассматривать его в отрыве от SharedPreferences и protobuf. Это просто файл с сериализатором и возможностью реактивной подписки.

#datastore
👍6🔥3