Javanese Online
596 subscribers
27 photos
1 file
94 links
Статьи и новости, наблюдения и советы.
Кодревью: http://javanese.online/%D1%80%D0%B0%D0%B7%D0%B1%D0%BE%D1%80_%D0%BA%D0%BE%D0%B4%D0%B0/
Обсуждение в @javanese_questions
Материалы пишет @Harmonizr
Download Telegram
Пять стадий написания LayoutManager

1. 😨 Быть такого не может, чтобы ранее такую штуку никто не делал!
2. 😡 Нет, ну почему никто не запилил?!
3. 🤬 Может, как-нибудь попроще, без менеджера?
4. 😢 *гуглинг в гитхабе*
5. 😌 Ладно-ладно, пойду писать.

Итого: Flow (раскладывает в строчку, переносит на новую при необходимости) с возможностью ограничить количество строк и показать специальную вьюшку «ещё 100500».

https://github.com/Miha-x64/FlowLayoutManager/
Gson 2.9.0

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

Моё внимание привлекли эти два изменения:
• Support EnumMap deserialization (#2071)
• Add GsonBuilder.disableJdkUnsafe() (#1904)

Вот и отлично! Не прошло и десяти л… а, нет, прошло :)
Того гляди, скоро и EnumSet начнёт поддерживать.

https://github.com/google/gson/blob/master/CHANGELOG.md
Adapter Delegates

Почему я не использую «классические» адаптерДелегаты?
Смотрим невооружённым глазом:
val adapter = ListDelegationAdapter<List<Animal>>(
catAdapterDelegate(...),
А если приподнять завесу «няшного» Kotlin DSL, то вообще увидим ЭТО:
public boolean isForViewType(List<Animal> items, int position) {
return items.get(position) instanceof Cat;
}
Напрашивается следующий вывод:
• можно забыть нужный делегат (крэш в рантайме),
• а можно забыть удалить лишний (недостижимый код),
• нельзя использовать несколько делегатов для одного типа данных.

Первые два пункта — классика: в Retrofit можно переехать с RxJava на suspend и забыть убрать CallAdapter.Factory, а в Gson есть «изкоробочные» адаптеры для, скажем, AtomicIntegerArray, которые психически здоровому человеку не нужны, а больному — вредны.
Третий пункт — ваще критикал: я хочу пару разных делегатов-заголовков, оба с типом CharSequence но разными шрифтами, отступами, размерами. Или в вертикальном списке имею горизонтальные списки всякого разного, которые сплошь instanceof List.

Короче, я создал, хоть и столько лет спустя.
Под капотом там просто сдвоенный List из элементов и их делегатов.
В комплекте валяется ItemDecoration, который могёт… а, не буду пересказывать скриншот, он в конце readme.

Лучи добра всем трудящимся!
Платформа для донатов

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

Всё, любой благотворительный проект может вылезти из пещерного века, выбросить камень, плуг, соху, десять карт разных банков и систем (потому что эквайринг) и гигабайт фоток чеков, снятых в темноте на телефон.
Опять про андроид. И снова сохранение состояния

Есть паттерн такой — назовём его custom composite view: наследуем любой подходящий layout, инфлейтим внутрь него <merge> и переиспользуем целиком. Описан в этой статье и много где ещё.

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

Нужно переопределить dispatch(Save|Restore)InstanceState, которые отвечают за сохранение-восстановление всего поддерева. Далее — вариации:
а) можно вызвать dispatch(Freeze|Thaw)SelfOnly(container), как это делает ресайклер, и в on(Save|Restore)InstanceState сделать всю нужную работу. Придётся унаследовать SavedState, к сожалению;
б) а можно взять какой-нибудь ParcelableSparseArray и передать его в super.dispatchSaveInstanceState и положить его в container под своим id, изолировав таким образом состояние всего компонента в отдельное пространство айдишек.
Курс по ФП на Java

Знаю, что «восьмёрке» уже восемь лет, но этот курс мне скинули буквально месяц назад. Функциональная композиция, стримы-коллекторы, CompletableFuture и прочие монадки в задачах и примерах.

https://stepik.org/course/91497/syllabus
Немного баготворительности. В каком сценарии сеттер сработает неверно?
Javanese Online
Немного баготворительности. В каком сценарии сеттер сработает неверно?
За три минуты и с первой попытки побеждает @italankin!
Прикрепляю фикс.
Javanese Online
Векторные иконки в Android: анатомия, патофизиология, диагностика и хирургия UPD: намутил промокод MikeGorunov2022JRGpc UPD: слайды
Вопрос, который мне не задали на конференции: является ли SVG path-data Тьюринг-полным языком?
Anonymous Quiz
41%
да
59%
не
IllegalStateException: cannot make a new request because the previous response is still open: please call response.close()

Занятный крэш меня сегодня посетил. Типовое обновление токена. Нетрудно понять, где именно баг, но как именно вы бы его чинили?
Полраза в жизни встречал ViewModel + MutableStateFlow. Попросили объяснить, как использовать. Чем дальше в лес, тем меньше понимания, как и зачем этим вообще пользоваться и под какими веществами это придумали 🤦

Ожидание:
val vm by viewModels(factory = { MyCoolViewModel(args) })

Реальность:
val vm: MyCoolViewModel by viewModels(factoryProducer = {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(...): T =
MyCoolViewModel(args) as T
}
})


Ожидание:
val stateSmth = savedStateHandle.getMutableStateFlow("smth", defaultSmth)

Реальность:
отрицание, гнев, торг, пост в канал, кастомный флоу.
Какой алгоритм решит мою задачу?

У нас есть один язык, который самостоятельно выбирает наиболее подходящие алгоритмы и структуры данных из имеющихся. Этим студентом был SQL. Если повезёт, задача выбора алгоритма сведётся к написанию SQL-запроса, а вывод EXPLAIN прямым текстом назовёт нужный алгоритм.
Традиционный пятничный анонс на выходные 4-5 февраля.

🌆Города, в которых на этой неделе пройдут встречи:

ANDROID:


🤖 МОСКВА
🤖 САНКТ-ПЕТЕРБУРГ

iOS:

🍏 АЛМАТЫ
🍏 АНТАЛЬЯ
🍏 АСТАНА
🍏 БАКУ
🍏 БАТУМИ
🍏 ВІЛЬНЯ
🍏 ЕКАТЕРИНБУРГ
🍏 ЕРЕВАН
🍏 ЛОНДОН
🍏 МИНСК
🍏 МОСКВА
🍎 ПОДГОРИЦА
🍏 РОСТОВ-НА-ДОНУ
🍏 САНКТ-ПЕТЕРБУРГ
🍏 СОЧИ
🍏 СТАМБУЛ
🍏 ТАШКЕНТ
🍏 ТБИЛИСИ
🍏 ЧЕРЕПОВЕЦ

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

Полезности:

👏 Ozon Tech Community Mobile Meetup в Алматы
👋 Опрос - Чем живете и дышите в мире IT
🤝 Ресурсы наших коллег
🧑‍💻 Наш LinkedIn

Ждём абсолютно всех.
Please open Telegram to view this post
VIEW IN TELEGRAM
OOM Resurrector

У меня на сервере постгрес раз в неделю съедает всю доступную память и своп. Настройки крутил, всё уменьшал, но нет никакого аналога нашего -Xmx, поэтому и нет гарантии, что постгря не падёт жертвой ООМ-киллера.

В интернете есть скрипт OOM-воскресителя, прикладываю свою версию, которая ориентируется на RAM+swap.

#!/bin/bash
THRESHOLD=1000 # MB
INTERVAL=60 # s

while :
do
free=$(free -tm | awk '/^Total:/{print $4}')
echo "Free $free""MB"

if [ "$free" -lt $THRESHOLD ]
then
systemctl restart postgresql@14-main
echo '$ systemctl restart postgresql@14-main'
fi

sleep $INTERVAL
done

Команда:
nohup ./pg-resurrect.sh > pg-resurrect.log &
О сколько нам открытий чудных готовит спека DNS!

Держу в курсе: доменное имя без точки в конце считается неполным, относительным. В эпоху умерших интранетов и локальных сетей об этом уже никто не вспомнит…
Обнаружено в процессе чтения исходников OkHttp.

https://webmasters.stackexchange.com/questions/73934/how-can-urls-have-a-dot-at-the-end-e-g-www-bla-de
Сравнительная характеристика JDBC и Android SQLite

API SQLite в Android сильно отличается от привычного для джавистов JDBC. Вот краткий справочник, что как называется и каким образом себя ведёт.

Помощник для миграции
В Android мы получаем базу данных от SQLiteOpenHelper, где есть коллбэки для миграции. В JDBC такой штуки нет, зато есть flywaydb.org и liquibase.org.

База данных
Отправная точка в Android называется SQLiteDatabase, в JDBC — DataSource, зачастую из HikariCP.

Соединение
JDBC: DataSource#getConnection() → Connection.
В Android же соединения спрятаны от нас, под капотом реализован пул.

Готовимся выполнить запрос
JDBC: Connection#createStatement() → Statement
Android: неть, не готовимся.

Выполнить запрос
JDBC: Statement#execute(String) → ResultSet
Android: SQLiteDatabase#(raw)Query(…) → Cursor

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

// JDBC
hikariPool.connection.use { conn ->
conn.createStatement().use { stmt ->
stmt.execute("SELECT …").use { rs ->
TODO()
}
}
}

// Android SQLite
db.rawQuery("SELECT …").use { cur ->
TODO()
}

Чтение
ResultSet и Cursor очень похожи, но в деталях есть разница. Например, ResultSet не знает своего размера.
И нужно не забыть закрыть Statement заодно с ResultSet.

Транзакции
Android SQLiteDatabase:
beginTransaction(NonExclusive)()
setTransactionSuccessful()
endTransaction()
JDBC Connection:
setAutoCommit(false)
commit() || rollback()
setAutoCommit(true)
Да, слово «транзакция» здесь вообще не фигурирует. Кстати, в одном из предыдущих постов у меня потекла транзакция именно потому что я забыл вернуть autoCommit.

Мутации
Для них удобнее всего подходят подготовленные запросы.
JDBC: Connection#prepareStatement(SQL) → PreparedStatement
Android: SQLiteDatabase#compileStatement(SQL) → SQLiteStatement
Тут принципиальной разницы нет.

Вре́менные таблицы и триггеры
Вот здесь спряталась хитрость. В любой СУБД временные таблицы существуют только в пределах соединения, их создавшего. Именно в SQLite есть ещё один нюанс — триггеры тоже локальны для соединения ввиду отсутствия сервера. В Android же это ограничение не действует, как если бы SQLiteDatabase оперировала лишь одним соединением. InvalidationTracker в Room тоже написан таким образом, будто этого органичения не существует. Почему оно так работает? Для меня это открытый вопрос.
Throwable — волшебный класс!

fillInStackTrace(): запомнить текущий стек.
Некоторые создают эксепшон и сразу же вызывают этот метод. Это бесполезно, он ведь и так вызывается в конструкторе.

А вот в каком-нибудь другом месте можно навязать любому эксепшону текущий стек. Удачной отладки!

Метод, кстати, нефинальный. Можно переопределить его и сделать no-op, получив дешёвый эксепшон без стека.

getMessage(), getCause(), printStackTrace(), toString() тоже нефинальные. Если хочется пострелять с двух рук, можно переопределить их и бросить оттуда новый exception.

setStackTrace(): вообще вседозволенность. Да, можно использовать его в благих целях, дабы прятать интринсики языка, а можно приколоться над коллегами!