Dolgo.polo Dev | Денис Долгополов – Telegram
Dolgo.polo Dev | Денис Долгополов
2.27K subscribers
88 photos
2 videos
120 links
Разбираемся в мобильной разработке (Android/iOS) и пытаемся выяснить, зачем оно так устроено

Статистика/цены: @dolgo_polo_dev_stats

По вопросам/рекламе: @dolgopolovdenis
Download Telegram
Dolgo.polo Dev | Денис Долгополов pinned «​​новая карта канала в давности обещал усовершенствовать карту канала. момент настал: • можно увидеть список всех вышедших постов на одном экране • с помощью поиска по заголовку и телу отыщатся посты на интересующие вас темы • киллерфича — кнопка "открыть…»
​​Kotlin Coroutine — корутины за 100 слов

Основные принципы:

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

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

Прекращаемые — можем прекратить выполнение функции с помощью job.cancel()

Функция замечает, что ее отменили, когда проверяет флаг Job.isActive


Основные понятия:

CoroutineScope — контейнер для корутин

дает возможность сгруппировать корутины, задать параметры CoroutineContext и отменить сразу все дочерние корутины

корутины могут быть запущены только внутри CoroutineScope

CoroutineContext — характеристики для CoroutineScope

Например, Dispatcher.Main - это наследник CoroutineContext - задает пул потоков, в котором будут выполняться функции

suspend — указывает, что функция может содержать асинхронный код

Job — объект, описывающий состояние корутины
Напримери, содержит флаг isActive

Deffered<T> — расширяет Job. Позволяет вернуть значение из корутины


Основные билдеры — функции, создающие и сразу запускающие корутину:

launch — возвращает Job

async — возвращает Deffered<T>

suspendCoroutine(cont: Continuation<T>) — возвращает T, который нужно вернуть через колбек cont.resume(obj: T)
👍253
ладно, возвращаемся

для начала не будет забавной пикчи, будет предположения о последствиях нового закона об IT

плюсы:

• освобождение от налогов — у компаний останется значительно больше дохода на развитие

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

• льготная ипотека сотрудникам — кредиты под хорошую ставку всегда хорошо

минусы:

• освобождение от налогов

тут нужно учитывать, что у государства не так много видов дохода. один из них — налоги

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

значит, нужно либо больше печатать, либо больше собирать с других, либо урезать расходы


• отсрочка от армии

вероятнее всего, в законе будет прописано, что отсрочку получат только те, кто отработал энное количество времени на организацию

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

не то чтобы это совсем нечестно, просто к этому нужно быть готовым


• льготная ипотека сотрудникам

риски аналогичны предыдущему пункту

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

опять же, это абсолютно честно, но бесконечный рост айтишных зарплат замедлит
👍2
​​Типы permissions для получения локации

Для работы с местоположением, нужно явно запросить одно из разрешений:

• ACCESS COARSE LOCATION

когда приложение находится в статусе foreground (открыта активити или запущен сервис с уведмолением) и нужно получить примерную локацию (точность порядка +-3 км)

• ACCESS FINE LOCATION

статус foreground и нужно получить точную локацию (порядка +-10-50м)

• ACCESS BACKGROUND LOCATION

можно получать локацию, когда приложение не имеет статуса foreground

такое нужно приложениям для семейного контроля, отслеживанию друзей или IoT-пультам


особенности:

• можно запросить только COARSE, но нельзя запросить только FINE

если вам нужно разрешение ACCESS FINE LOCATION, запрашивайте сразу оба, а пользователь выберет - дать точную локацию, приближенную или отказать


• если пользователь уже выдал COARSE, можно запросить апгрейд до FINE

для этого снова выполните запрос на оба разрешения


• запрашивать ACCESS BACKGROUND LOCATION можно после получения одного из foreground-разрешений

при этом, если получено только COARSE, то только эта точность и будет доступна в background


....

описанные механики разнятся от версии к версии андроида
приведенная логика справедлива для Android 12, в более старых некоторые требования мягче
👍11🔥2
​​Философия LayoutParams

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

свойства LayoutParams:

• базовый класс — ViewGroup.LayoutParams

он содержит только два свойства - height, weight


• View получают LayoutParams от ближайшего родительского ViewGroup

если родитель вьюшки - RelativeLayout, то View.mLayoutParams = RelativeLayout.LayoutParams

если у вьюшки нет родителя (она еще не attached), то View.mLayoutParams = null


• ViewGroup.LayoutParams расширяется наследниками

например, есть такая иерархия: RelativeLayout.LayoutParams extends ViewGroup.MarginLayoutParams extends ViewGroup.LayoutParams


• если вызвать View.setLayoutParams(...), то View передаст новые параметры родителю, тот перерисует себя, а после перерисуется сама View

....

какие отрицательные побочки мы получаем от наследовательной архитектуры LayoutParams? в чем ее минусы (из-за которых набирает популярность compose)?
👍6
​​Xml — зачем префиксы android, tools, app

Описывая вьюшки в xml-файлах, мы чаще всего пишем android:paramName="value"

В этом случае android — это префикс

Префикс принято объявлять в самом старшем элементе xml-файла — используется следующая конструкция:

xmlns:prefixName="URI"

Зачем?

чтобы можно было легко подменять способ применения параметра, не изменяя его имени

например, параметр text для TextView

• если написать, android:text="value", то после запуска приложения мы увидим в TextView текст "value"

• если написать tools:text="value", то в редакторе студии мы увидим в TextView текст "value", а при запуске приложения - нет

один и тот же параметр обработался по-разному


Существуют следующие префиксы:

• android — параметры, которые повлияют на работу приложения

• tools — параметры, которые учитываются Lint и визуальным редактором студии, но вырезаются при компиляции

• app — создание собственных параметров (или использования кастомных параметров из библиотек)

....

почему ссылка из объявления префикса не открывается?
например, xmlns:app="http://schemas.android.com/apk/res-auto"
👍17🤩1
​​Философия LayoutInflator

LayoutInflator — класс, умеющий создавать из xml-разметки объект View

По умолчанию он использует стандартный парсер XmlPullParser, но можно передать свой (зачем - придумать сложно)


Способности:

• inflate(...) — парсит xml-файл в View-объект

если передать ссылку на родителя-ViewGroup, то LayoutInflator автоматически прикрепит View к родителю и вернет ссылку не на View, а на этого родителя

при передачи родителя View получает родительский LayoutParams. если родителя нет — View.mLayoutParams = null

createView(...)

может создать нужную View без xml-файла


замечание:

findViewById(...)
— не парсит xml, а осуществляет поиск по уже созданным View
👍27🔥1🎉1
​​Kotlin Operator

Еще одна возможность котлина, которой можно придумать применение в своем проекте

чтобы коллеги потом ходили и спрашивали, чего ты там такой умный понапридумывал

Как работает:

1. помечаем функцию словом operator

2. даем ей одно из заложенных в языке названий

например, unaryPlus() связана с оператором "+"

3. пишем реализацию этой функции — все, что душе угодно

Например (напишем через extensions-функцию — гулять так гулять):

operator fun User.unaryPlus() : User {
this. name = this. name + " vot eto ya molodec"
return this
}

используем:
user. name = "Ivan"
print( (+user).name) )

получаем:
Ivan vot eto ya molodec


также доступны функции:

• unaryMinus() == "-"
• not() = "!"
• plus() == a "+" b
• rangeTo() == a".."b

и другие

....

где в реальном проекте этому можно найти хорошее применение?
👍9🔥7😁3🎉1
​​URL vs URI

При работе с файлами или интернетом можно заметить, что иногда нужно указывать URL, а иногда — URI. а почему?

URI — это более общее понятие, включающее в себя URL и URN

UR* - Uniform Resource
• I - Identifier
• L - Location
• N - Name

значит:

• URI может являться и путем до файла, и именем файла
• URL — указывает расположение файла (причем обычно в сети Интернет, кхе)
• URN — имя файла (без пути до него)


URL/URI имеют схожую структуру:

scheme://authoritypath?query# fragment

• scheme — протокол (например, http: или mailto: или tel:)
• authority — логин или хост
• path — путь до файла, разделенный косыми чертами
• query — слова-фильтры
• fragment — произвольный параметр

URI могут быть абсолютные или относительные (вести куда-то, относительно папки, в которой будут открыты)

....

чем отличаются две scheme, встречающиеся при работе с файлами в андроиде — content и file?
10🔥10👍9
​​MVI — архитектура за 100 слов

Этот подход набирает все большую популярность, вытесняя MVVM

Яркий признак MVI — наличие State
State
— неизменяемое состояние (объект, который называет состояние и передает все связанные с ним данные)

Например, ErrorState(val errorMessage: String)

Слои:

• Model — источник State для View
• View — принимает State и отображает
• Intent — события

Не буду вдаваться в философию, приведу пример:

когда пользователь открывает экран, Model создает State.Defult
загружает в этот State всю информацию, которую нужно отобразить, и отдает View

через некоторые время пользователь нажимает кнопку
View сообщает об новом Intent в Model
Model
формирует новый State и отдает View

В целом — это все. Но мир не идеален, поэтому:

Intent можно разделить на 2 группы:

• Действия юзера
• События от бекенда (например, пришло новое сообщение)

State можно поделить на 2 группы:

• Стабильные — должны отображаться даже после переворота экрана
• Эффекты — должны отобразиться один раз (например, Toast\Snackbar)

Плюсы MVI:
• наглядность — прямой "поток" данных и событий, без макарон
• модульность — тестируемость
• заранее прописываешь стейты, так что не забываешь учесть разные состояния (обычно State.Default, State.Failed, State.Loading, State.Success)

Минусы MVI:
• часто приходится делать тяжелый моральный выбор - если на экране появляются новые вьюшка, то нужно создавать новый State или модифицировать старый?
👍28🔥82
Прошло несколько месяцев — мы праздновали первую сотню человек в нашей тусовочке — теперь масштабы серьезнее

Теперь мы поднимаем кружки кофе и пива за первую 1000 человек — торжественное ура

.....

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

А мне желаю наконец-то найти классных рекламодателей — это будет следующий большой рывок в развитии этой тусовки

🍻🤝🍻

.....

в ближайших планах:

• продолжаю равно то же самое, пытаюсь удержаться от ввода новых рубрик. еще не время)

• появится форма обратной связи, через которую можно будет влиять на темы следующих постов
👍15🎉7🔥51🤩1🤮1
​​Порядок инициализации полей, конструкторов и блоков

Рассмотрим самый сложный пример — представим, что у нас есть наследование, статика, конструкторы и поля (+ companion object, + init)

Порядок инициализации в Java и Kotlin немного различны, хотя и схожи по общей идее

Java:

Статические и нестатические поля и блоки инициализируются в порядке объявления (чем выше строчка, тем первее она будет инициализирована)

1. Статические поля и блоки родителя
2. Статические поля и блоки ребенка
3. Нестатические поля и блоки родителя
4. Конструктор родителя
5. Нестатические поля и блоки ребенка
6. Конструктор ребенка

Kotlin

В котлине нет статики, нельзя написать код блока вне функции, но появляются companion object и init-функция

1. Поля внутри companion object родителя
2. Поля внутри companion object ребенка
3. Поля и блок init родителя (в порядке объявления)
4. Конструктор родителя
5. Поля и блок init ребенка (в порядке объявления)
6. Конструктор ребенка

....

когда приходится использовать статические или нестатические блоки кода в боевых проектах?

в какой момент инициализируется статическое поле, если к нему обратиться, не создавая объект класса? как это повлияет на порядок инициализации в момент создания объекта класса?
👍293🤩1
​​Task или виды Activity launch mode

У каждого приложения может быть несколько стеков из Activity. Под каждый стек создается новая Task

Activity launch mode
— описывает сценарий изменения стека при запуске новой Activity

Например, можно открывать каждую Activity в отдельном стеке. Тогда в списке запущенных приложений (Recent screen) вы увидите, что ваше приложение открыто в нескольких окнах, каждое из которых можно закрыть отдельно


Выбрать, что произойдет после запуска новой Activity, можно с помощью тега launchMode у <activity> в Manifest. И при помощи установка флага в Intent. А еще при помощи параметра taskAffinity, allowTaskReparenting, clearTaskOnLaunch, alwaysRetainTaskState, finishOnTaskLaunch....

Все гениальное просто, да?
Но разберем основные варианты, которые покрывают 99,99% случаев:

• standart

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

• singleTop

если в текущей таске Activity находится на вершине, то новый ее инстанс создан не будет. текущий инстанс получит Intent через onNewIntent()

в противном случае создастся новый экземпляр в текущей таске

• singleTask

если уже есть таска, в которой вызываемая Activity находится в корне стека, то откроется экземпляр этой Activity, ей будет передан Intent через onNewIntent(), а все остальные Activity в этой таске будут задестроены

в противном случае создается новая таска с новым Activity

• singleInstance

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

• singleInstancePerTask

аналогично singleTask. отличие - если добавить флаг FLAG ACTIVITY MULTIPLE TASK, запуск новой Activity может вызвать создание новой таски, даже если уже есть таска с инстансом этой Activity в корне стека

• standart + FLAG ACTIVITY CLEAR TOP

если в текущей таске есть запускаемая Activity, то она получит Intent через onNewIntent(), а все Activity, запущенные после нее, будут задестроены

в противном случае будет создан новый экземпляр Activity в текущей таске

....

1. есть Activity, есть Task. а зачем тогда вводится и за что отвечает абстракция Window?

2. все вышеописанное справедливо для приложения с множеством Activity. а возможно ли добиться создания нескольких Task в single activity приложении на Fragments/Compose?
👍142😁1🤩1
​​Как придумать свои единицы измерения на примере Compose

Вероятно, это начало серии постов о Compose. Начнем с малого

В Compose можно использовать привычные единицы измерения:
• 18.dp
• 18.sp
• 18.em

Но почему не приходится писать Dp(18) или Dp.Builder().get(18)?

Фокус в extensions-функциях, созданных для всех численных примитивов (Int, Float, Double)

Например:

inline val Int.dp: Dp get() = Dp(value = this.toFloat())

(если вы практикуете только Java, вам может стать плохо, это нормально)

Разберем каждую часть строчки:

• inline — слово, заставляющее компилятор на этапе компиляции упростить код за счет замены лямбда-выражения на последовательный код

если слово inline стоит перед переменной (как в текущем примере), то оно относится к функциям get() и set() переменной

• .dp: Dp — создание переменное типа Dp в классе Int (расширили класс Int новой переменной)

• get() — переопределили зарезервированную функцию get() для переменной dp

• this — ссылка на класс Int, то есть в случае 18.dp ссылка на класс Int(18)

• value — название параметра в конструкторе класса Dp (просто для наглядности)

вот так, использовав всего 5 трюков котлина в одной строчке, можно придумать собственную единицу измерения

....

в боевом проекте такую фишку можно применить для управления, например, балансом юзера — 18.rub + 20.dol = 30.eur. еще идеи?
🔥145👍3😁2
​​Thread Safe — когда пора начать об этом думать

Понятие Thread Safe имеет множество определений, но суть у них одна:

если переменную изменяют несколько потоков, то результат изменения должен быть предсказуемый

(если b = 1, и мы дважды вызываем b++, то ожидаем увидеть b = 3 — это и есть предсказуемость)


Что может пойти не так?

Во-первых, по умолчанию потоки работают с переменной не в общей памяти, а создают свою локальную копию. А после всех манипуляций с объектом внутри потока возвращают объект в общую память

То есть другие потоки смогут увидеть изменения в объекте только после завершения этапа работы первого потока


Во-вторых, один поток может взять переменную и начать изменять ее. Например, была строка var s = "Big", а на выходе должна получится строка s = s + "Hot"

Но пока первый поток не закончил изменение s, второй поток тоже считал s. И решил сделать из нее s = s + "Dog"

Получается, что мы хотели прибавить к "Big" строки "Hot" и "Dog", а получим на выходе не "BigHotDog", а "BigHot" или "BigDog" или "BigDogHot" — результат непредсказуем


Ответ на заголовок поста:

пора начинать разбираться в многопоточности, как только в проекте появляется два и более потоков, работающих с общими переменными

....

с чего стоит начать, если хочешь понять эту вашу многопоточность вдоль и поперек?
👍244
​​Как перехватывать Exception в Coroutine. Часть 1

Поймать Exception в Java и Kotlin просто — ошибка, выброшенная через throw, поднимается вверх по стеку функций, пока не попадет в try-catch блок или в Thread.UncaughtExceptionHandler


Но как только появляются корутины...

Это одна из самых сложных тем по двум причинам:

• зависимость пути проброса Exception от множества условий

• минимальное количество примеров (в документации и интернете в целом), покрывающих неочевидные кейсы


На логику обработки ошибок в корутинах влияет:

• создаем корутину верхнего уровня или вложенную

• используем ли мы Job или SupervisorJob

• используем ли мы билдер launch или async

• устанавливаем ли мы CoroutineExceptionHandler (и куда устанавливаем)

• еще и особая CancellationException обрабатывается по другим правилам


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

....

в следующих частях разберемся, зачем столько сложностей, и создадим единую логику отслеживания ошибок
👍22🔥21😱1🎉1
​​Как перехватывать Exception в Coroutine. Часть 2

Разберемся с тем, как формируется контекст корутины

CoroutineContext — параметры корутины или CoroutineScope:

Job или SupervisorJob
Dispatcher (IO, Main, Default...)
CoroutineExceptionHandler (или его отсутствие)
CoroutineName (параметр для дебага)


Алгоритм формирования для CoroutineScope:

• стандарные параметры + унаследованные + указанные в билдере

Алгоритм формирования для корутины:

• стандарные параметры + унаследованные + указанные в билдере + Job

при этом слагаемые справа переопредяют предыдущие


Пример:

CoroutineScope() {
launch(Dispatcher.Main
+ SupervisorJob()) {
launch(Dispatcher.Default)
{}
}
}

Контекст для CoroutineScope:

Dispatcher.Default, CoroutineName("coroutine"), Job1

для внешнего launch:

Dispatcher.Main, CoroutineName("coroutine"), Job2

для внутреннего launch:

Dispatcher.Default, CoroutineName("coroutine"), Job3


На что обратить внимание:

• у внешнего launch в контексте исчезает SuperviserJob, потому что при формирование прибавляется Job справа

то есть установит SuperviserJob можно только в CoroutineScope


• между всеми Job установлены parent-child отношения

если вызвать Job1.cancel(), то Job1 и Job2 тоже будут остановлены

а если в launch явно передать Job(), то взаимоотношения разрушаться. родитель не будет ожидать завершения работы такого ребенка, и останавливать его не будет
👍71
​​А теперь к новостям канала

1. Появился бот приема обратной связи для выбора темы следующих постов — t.me/dolgopolobot

Если есть идея, о чем было бы интересно почитать в следующем посте, то можно оставить сообщение этому боту с тегом #idea_dpd

Принимаются темы любой сложности и актуальности — от "вот бы был пост про синхронизацию потоков и поддержку OS Android 2.1 в 2022 году" до "а какой жизненный цикл у Fragment?!"


2. Выходит в свет мой второй канал — там картинки со смешными местами из путешествий и рассказы об интересных штуках, о которых невозможно молчать

Отдыхать от программирования сюда — t.me/dolgo_polo_ahaha


3. Оказалась, есть часть людей, которая пользуется Яндекс.Дзеном

Игнорировать это больше нельзя, поэтому постепенно там (тут — zen.yandex.ru/android_dpd) будут появляться старые посты

Хотя есть подозрение, что эти посты многие из вас не видели. Кто вообще листает канал выше пяти последних сообщений?
6👍4
​​Как перехватывать Exception в Coroutine. Часть 3

Факты, которые нам понадобятся:

• корутина верхнего уровня — корутина, чьим родителем является Scope, а не другая корутина

scope.launch { — верхнего уровня
launch { } — не верхнего
}


CoroutineExceptionHandler — получатель необработанных ошибок

Его можнно назначить в конструкторе Scope или в билдере launch() верхнего уровня, если его родитель Scope(SupervisorJob)


• два понятия, которые нельзя путать:

throws (выбрасывать)

если ошибка выброшена, то она поднимается по стеку вызова функций, пока не попадется в try-catch или Thread.UncaughtExceptionHandler


propagate (распространять)

если ошибка распространяется, то она игнорирует блоки try-catch и передается от дочерний корутины к родителю

если ошибка добралась до коренного Scope, а у него не установлен CoroutineExceptionHandler, то ошибка передается в Thread.UncaughtExceptionHandler


• при создании Scope можно назначить два вида Coroutine Context:

Job — если внутри Scope появляется ошибка, не перехваченная try-catch, то Scope завершает работу всех дочерних корутин, завершается сам и распространяет ошибку

SupervisorJob — если внутри Scope появляется ошибка, не перехваченная try-catch, то Scope распространяет ошибку, не завершая дочерние корутины и себя


CancellationException — особый вид ошибки. Выбрасывается, когда корутина была остановлена с помощью cancel()

Она, в отличие от остальных ошибок, не останавливает родительский Scope и не влияет на работу остальных корутин в Scope

....

если какой-то из пунктов вызывает вопросы — готов расписать подробнее в комментариях)

и нам наконец хватает знаний, чтобы перейти к практике в следующем посте
5👍5🎉1
​​Как перехватывать Exception в Coroutine. Часть 4

Разберемся с билдером launch

Рабочие способы перехватить ошибки:

• внутри launch

launch {
try {
throw Exception()
}
catch(e: Exception) {}
}


• с помощью перехватчика необработанных ошибок, установленного в Scope или launch верхнего уровня

scope.launch(handler) {
throw
Exception()
}

СoroutineScope(handler) {
launch {
throw
Exception()
}

}

в этом способе важно понимать — если родитель Scope(Job), дети этого Scope будут остановлены

если родитель Scope(SupervisorJob), дети этого Scope продолжат работу

если родитель корутина, то дети этой корутины будут остановлены в любом случае



Что будет, если не обработать:

ошибка будет распространена родительским корутинам и Scope, пока не попадет в CoroutineExceptionHandler или Thread.UncaughtExceptionHandler



Что не получится сделать:

• обернуть launch в try-catch

такое не сработает:

try {
launch
{ throw Exception() }
} catch(e: Exception) {}

потому что launch распространяет ошибку родителю, а не выбрасывает


....

если вы встретили примеры, в которых не очевидно поведение корутин при возникновении ошибки — скидывайте их в комментарии, разберем)
7👍4🎉1
​​Как перехватывать Exception в Coroutine. Часть 5 (финал)

Разберемся с билдером async

Рабочие способы обработки ошибки:

• внутри async

async
{
try
{
throw Exception()
}
catch(e: Exception) {}
}

• с помощью try-catch, если async верхнего уровня

Ошибка выбрасывается во время вызова await()

runBlocking
{
val job = scope.async {
throwError()
}

tryCatch {
job.await()
}
}


Что будет, если не обработать:

ошибка будет распространяться родительским корутинам и Scope, пока не попадет в CoroutineExceptionHandler или Thread.UncaughtExceptionHandler

....

в комментариях оставлю ссылку на Kotlin Playground с тестами, на которых можно проверить свое чутье — где и кем будет перехвачена ошибка
🔥51