Как перехватывать 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(), то взаимоотношения разрушаться. родитель не будет ожидать завершения работы такого ребенка, и останавливать его не будет
Разберемся с тем, как формируется контекст корутины
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(), то взаимоотношения разрушаться. родитель не будет ожидать завершения работы такого ребенка, и останавливать его не будет
👍7❤1
А теперь к новостям канала
1. Появился бот приема обратной связи для выбора темы следующих постов — t.me/dolgopolobot
Если есть идея, о чем было бы интересно почитать в следующем посте, то можно оставить сообщение этому боту с тегом #idea_dpd
Принимаются темы любой сложности и актуальности — от "вот бы был пост про синхронизацию потоков и поддержку OS Android 2.1 в 2022 году" до "а какой жизненный цикл у Fragment?!"
2. Выходит в свет мой второй канал — там картинки со смешными местами из путешествий и рассказы об интересных штуках, о которых невозможно молчать
Отдыхать от программирования сюда — t.me/dolgo_polo_ahaha
3. Оказалась, есть часть людей, которая пользуется Яндекс.Дзеном
Игнорировать это больше нельзя, поэтому постепенно там (тут — zen.yandex.ru/android_dpd) будут появляться старые посты
Хотя есть подозрение, что эти посты многие из вас не видели. Кто вообще листает канал выше пяти последних сообщений?
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
....
если какой-то из пунктов вызывает вопросы — готов расписать подробнее в комментариях)
и нам наконец хватает знаний, чтобы перейти к практике в следующем посте
Факты, которые нам понадобятся:
• корутина верхнего уровня — корутина, чьим родителем является 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 распространяет ошибку родителю, а не выбрасывает
....
если вы встретили примеры, в которых не очевидно поведение корутин при возникновении ошибки — скидывайте их в комментарии, разберем)
Разберемся с билдером 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 с тестами, на которых можно проверить свое чутье — где и кем будет перехвачена ошибка
Разберемся с билдером 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 с тестами, на которых можно проверить свое чутье — где и кем будет перехвачена ошибка
🔥5❤1
GET vs POST vs PUT в Retrofit 2 — уровень "Мобильный разработчик"
Вероятно, если вы человек-бекендер, то нужно углубляться сильнее. А для нас достаточно следующих фактов:
Указывая аннотации @GET, @POST или @PUT, вы определяете, в каком виде будут переданы параметры запроса на сервер
• GET
Параметры запроса вставляются в URL запроса с помощью аннотации Query(parameterName) в формате key=value
Параметры добавляются после знака "?"
Длина URL ограничена 2048 символами
Например, на выходе можете получить такой запрос: https://www.google.com/search?q=tg+dolgo.polo+dev
key = q
value = tg+dolgo.polo+dev
• POST
Параметры запроса вставляются не в URL, а в поле requestBody с помощью аннотации @Body в формате key=value
POST-запросом можно отправить файл, так как он поддерживает отправку бинарных данных
Например, на выходе можете получить такой запрос: https://www.google.com/search
Параметры при этом передаются отдельно в поле requestBody
• PUT
То же самое, что и POST. Нужен для логического разделения ролей запросов в API (просто другое название)
POST — обновляет данные по какому-то правилу
например, увеличивает количество яблок в коробке: apples++
PUT — устанавливает данные
например, устанавливает количество яблок в коробке: apples = 1
Поэтому говорят, PUT — идемпотентный — можно отправлять один и тот же запрос много раз, данные изменятся только один раз
Еще есть мнение, что POST для списков, а PUT для обращения по конкретному id
....
а менее известные DELETE, PATCH, OPTIONS, CONNECT и TRACE зачем и когда используются?
Вероятно, если вы человек-бекендер, то нужно углубляться сильнее. А для нас достаточно следующих фактов:
Указывая аннотации @GET, @POST или @PUT, вы определяете, в каком виде будут переданы параметры запроса на сервер
• GET
Параметры запроса вставляются в URL запроса с помощью аннотации Query(parameterName) в формате key=value
Параметры добавляются после знака "?"
Длина URL ограничена 2048 символами
Например, на выходе можете получить такой запрос: https://www.google.com/search?q=tg+dolgo.polo+dev
key = q
value = tg+dolgo.polo+dev
• POST
Параметры запроса вставляются не в URL, а в поле requestBody с помощью аннотации @Body в формате key=value
POST-запросом можно отправить файл, так как он поддерживает отправку бинарных данных
Например, на выходе можете получить такой запрос: https://www.google.com/search
Параметры при этом передаются отдельно в поле requestBody
• PUT
То же самое, что и POST. Нужен для логического разделения ролей запросов в API (просто другое название)
POST — обновляет данные по какому-то правилу
например, увеличивает количество яблок в коробке: apples++
PUT — устанавливает данные
например, устанавливает количество яблок в коробке: apples = 1
Поэтому говорят, PUT — идемпотентный — можно отправлять один и тот же запрос много раз, данные изменятся только один раз
Еще есть мнение, что POST для списков, а PUT для обращения по конкретному id
....
а менее известные DELETE, PATCH, OPTIONS, CONNECT и TRACE зачем и когда используются?
👍20❤4🐳2👎1🙏1
Чек-лист профессионального программиста
Это свод правил, выработанный за годы фриланса
Когда соблюдаю их, приложение получается удобным для юзера
Да, этот список не сделает из вас сеньора, как и не делает им меня. Но это как с мужем-на-час, который не только починил кран, но и убрал за собой рабочее место — сразу видно, крутой
• сортировать списки
если список выводится пользователю, нужно не забыть понятную сортировку - по дате создания или алфавиту, если нет других заданных параметров
• проверять нули, пустые строки и нуллы, количество элементов в списке
продумайте, что увидит юзер. например, если список пуст, не забыть вывести "оëой, данных пока нет"
и не выводить "ваша скидка 0% !!!"
• поднимать ошибку по стеку в обертке
если вы запрашиваете Long из базы данных, то возвращать -1 в случае ошибки не лучшая идея. Лучше вернуть Success(value) или Error(errorCode)
• использовать варианты toDoubleOrNull() вместо toDouble()
никогда не надейтесь, что преобразование строки в число пройдёт успешно
а вдруг кто-то подсунет строку с запятой вместо точки?
приложение крашнется с ошибкой NotNumber, а юзер умрёт от инфаркта
лучше добавить лишнюю проверку на null (или обернуть в try-catch)
• сообщать о загрузке
если контент загружается больше 500мс, отобразите индикатор загрузки или скелет будущего контента
• помнить, что юзер может закрыть приложение в любой момент, но операция должна завершиться
юзер не обязан ждать, пока данные сохранятся - может свернуть приложение, заблокировать телефон, уронить его в лужу - операция должна выполниться в фоне
Это свод правил, выработанный за годы фриланса
Когда соблюдаю их, приложение получается удобным для юзера
Да, этот список не сделает из вас сеньора, как и не делает им меня. Но это как с мужем-на-час, который не только починил кран, но и убрал за собой рабочее место — сразу видно, крутой
• сортировать списки
если список выводится пользователю, нужно не забыть понятную сортировку - по дате создания или алфавиту, если нет других заданных параметров
• проверять нули, пустые строки и нуллы, количество элементов в списке
продумайте, что увидит юзер. например, если список пуст, не забыть вывести "оëой, данных пока нет"
и не выводить "ваша скидка 0% !!!"
• поднимать ошибку по стеку в обертке
если вы запрашиваете Long из базы данных, то возвращать -1 в случае ошибки не лучшая идея. Лучше вернуть Success(value) или Error(errorCode)
• использовать варианты toDoubleOrNull() вместо toDouble()
никогда не надейтесь, что преобразование строки в число пройдёт успешно
а вдруг кто-то подсунет строку с запятой вместо точки?
приложение крашнется с ошибкой NotNumber, а юзер умрёт от инфаркта
лучше добавить лишнюю проверку на null (или обернуть в try-catch)
• сообщать о загрузке
если контент загружается больше 500мс, отобразите индикатор загрузки или скелет будущего контента
• помнить, что юзер может закрыть приложение в любой момент, но операция должна завершиться
юзер не обязан ждать, пока данные сохранятся - может свернуть приложение, заблокировать телефон, уронить его в лужу - операция должна выполниться в фоне
👍27❤7🏆4🤔2
Стартовал мой второй серьезный проект — место, где можно выкладывать свои идеи и очень обоснованно критиковать чужие
tg: mvpшная
• зачем это нужно?
появляется идея. она кажется тебе неплохой (принесет успех, деньги и внутренний покой), но ведь оцениваешь ее только через призму своего опыта
а можешь сразу обсудить ее с другими крутыми людьми, которые увидят слабые места
например, можете залететь с идеями своих приложений или сервисов. узнаете, на чем можете споткнуться. или найдете инвесторов исобутыльников единомышленников
"а вдруг украдут!" - если у вашу идею можно легко украсть, то либо это уже делают тысячи людей, либо вы что-то упускаете в расчетах. и как раз обсуждение с незнакомыми людьми поможет узнать, что забываешь учесть
tg: mvpшная
• зачем это нужно?
появляется идея. она кажется тебе неплохой (принесет успех, деньги и внутренний покой), но ведь оцениваешь ее только через призму своего опыта
а можешь сразу обсудить ее с другими крутыми людьми, которые увидят слабые места
например, можете залететь с идеями своих приложений или сервисов. узнаете, на чем можете споткнуться. или найдете инвесторов и
Telegram
mvpшная
Место, где можно опубликовать идею своего бизнеса (любого размера) и получить качественный фидбек
Читателям предлагается безжалостно указывать на все слабые места и будущие проблемы идеи в комментариях
@messages_receiver — прием заявок
Читателям предлагается безжалостно указывать на все слабые места и будущие проблемы идеи в комментариях
@messages_receiver — прием заявок
👍3❤1😁1🤔1
Kotlin Flow: StateFlow vs SharedFlow
Часть №1
Сначала общие факты для понимания задумки авторов библиотеки Kotlin Flow:
• Flow (потоки) — никак не связаны с Android
это библиотека котлина, которая завязано только на корутины (которые тоже являются библиотекой, не связанной с Android SDK)
• основные методы:
collect() - подписаться на данные из потока
emit() - положить значение в поток
• холодный поток — запускает создание элементов каждый раз при вызове collect()
при объявление потока описывается функция-билдер новых элементов, внутри которого вызывается emit()
то есть для каждого подписчика свой поток данных
• горячий поток — не зависит от вызова collect()
то есть для каждого подписчика один и тот же поток данных
• если корутина, запустившая холодный поток, остановлена — поток тоже остановится
но только если внутри есть проверка флага isActive
• collect() и emit() — suspend-функции, выполнение корутины не продолжится, пока они не завершатся
collect() приостанавливает корутину до завершения потока данных
emit() приостанавливает корутину до момента, пока поток данных не будет готов принять элемент (освободится буфер)
• CoroutineContext определяется родительской корутиной, вызвавшей collect(). но его можно сменить с помощью оператора flowOn(CouroutineContext)
например, можно получать элементы из Flow в главном потоке, а выполнять их создание в бэкграунде
• зачем нужен буфер?
если речь идет о холодном потоке, то буфер помогает выполнить emit(), не дожидаясь завершения collect()
если речь идет о горячем потоке, то буфер определяет, сколько последних элементов получит новый подписчик, вызвавший collect()
Часть №1
Сначала общие факты для понимания задумки авторов библиотеки Kotlin Flow:
• Flow (потоки) — никак не связаны с Android
это библиотека котлина, которая завязано только на корутины (которые тоже являются библиотекой, не связанной с Android SDK)
• основные методы:
collect() - подписаться на данные из потока
emit() - положить значение в поток
• холодный поток — запускает создание элементов каждый раз при вызове collect()
при объявление потока описывается функция-билдер новых элементов, внутри которого вызывается emit()
то есть для каждого подписчика свой поток данных
• горячий поток — не зависит от вызова collect()
то есть для каждого подписчика один и тот же поток данных
• если корутина, запустившая холодный поток, остановлена — поток тоже остановится
но только если внутри есть проверка флага isActive
• collect() и emit() — suspend-функции, выполнение корутины не продолжится, пока они не завершатся
collect() приостанавливает корутину до завершения потока данных
emit() приостанавливает корутину до момента, пока поток данных не будет готов принять элемент (освободится буфер)
• CoroutineContext определяется родительской корутиной, вызвавшей collect(). но его можно сменить с помощью оператора flowOn(CouroutineContext)
например, можно получать элементы из Flow в главном потоке, а выполнять их создание в бэкграунде
• зачем нужен буфер?
если речь идет о холодном потоке, то буфер помогает выполнить emit(), не дожидаясь завершения collect()
если речь идет о горячем потоке, то буфер определяет, сколько последних элементов получит новый подписчик, вызвавший collect()
❤19👍13❤🔥1🥰1
Kotlin Flow: StateFlow vs SharedFlow
Часть №2
Общее для StateFlow и SharedFlow:
• это горячие потоки
то есть вызов collect() лишь позволяет собрать данные, которые уже в них есть или поступят в них, но не запускает выполнение какого-либо билдера (как это происходит с холодными потоками)
• они не отслеживает жизненный цикл активити\фрагмента\вьюшки
но их можно запустить в корутинах, которые автоматически остановятся во время вызова onStop(). остановка \корутины вызывает остановку выполнения collect()
Отличия StateFlow и SharedFlow:
• при создании StateFlow нужно указать начальное значение, а при создании SharedFlow — нет
• у SharedFlow можно настроить вместимость буфера, у StateFlow хранится только 1 последний элемент
У SharedFlow два буфера — replayBuffer и extraBuffer:
• replay — сколько новый подписчик, вызвавший collect(), получит последних выпущенных элементов
• extraBuffer — сколько дополнительно элементов сохранится для "медленного" подписчика, который не успевает выполнять collect()
этот буфер работает в следующей ситуации:
допустим, emit() вызывается каждые 100мс, а collect() выполняется за 150мс
• onBufferOverflow — определяет, что делать, когда вызывается emit() при полностью заполненном extraBuffer
сценарии:
SUSPEND (остановить корутину на строчке emit, пока не освободится буфер)
DROPLATEST (удалить последний элемент в буфере)
DROPOLDEST (удалить самый старый элемент в буфере)
эти сценарии включаются в работу только если есть хотя бы один подписчик, который не успевает выполнять collect(). они не влияют на работу replayBuffer
Часть №2
Общее для StateFlow и SharedFlow:
• это горячие потоки
то есть вызов collect() лишь позволяет собрать данные, которые уже в них есть или поступят в них, но не запускает выполнение какого-либо билдера (как это происходит с холодными потоками)
• они не отслеживает жизненный цикл активити\фрагмента\вьюшки
но их можно запустить в корутинах, которые автоматически остановятся во время вызова onStop(). остановка \корутины вызывает остановку выполнения collect()
Отличия StateFlow и SharedFlow:
• при создании StateFlow нужно указать начальное значение, а при создании SharedFlow — нет
• у SharedFlow можно настроить вместимость буфера, у StateFlow хранится только 1 последний элемент
У SharedFlow два буфера — replayBuffer и extraBuffer:
• replay — сколько новый подписчик, вызвавший collect(), получит последних выпущенных элементов
• extraBuffer — сколько дополнительно элементов сохранится для "медленного" подписчика, который не успевает выполнять collect()
этот буфер работает в следующей ситуации:
допустим, emit() вызывается каждые 100мс, а collect() выполняется за 150мс
• onBufferOverflow — определяет, что делать, когда вызывается emit() при полностью заполненном extraBuffer
сценарии:
SUSPEND (остановить корутину на строчке emit, пока не освободится буфер)
DROPLATEST (удалить последний элемент в буфере)
DROPOLDEST (удалить самый старый элемент в буфере)
эти сценарии включаются в работу только если есть хотя бы один подписчик, который не успевает выполнять collect(). они не влияют на работу replayBuffer
👍18🔥5❤1
Рассказ о стартапе
У меня появилась боль — когда я хотел потратить свободное время, у меня из головы вылетали все идеи
Начал вести список развлечений — оказалось, что без фильтров это очень неудобно
Решил делать приложения (не зря же умею). А чтобы оно не умерло до релиза, решил обернуть это все в стартап и завести отдельный канал, где рассказываю о трудностях в разработке
Если вам интересно, с какими факапами можно столкнуться при запуске своего проекта (от поиска иконок до оформления документов) и пройти путь от зарождения идеи до релиза (или удаления папки с проектом) со мной, то раз в пару дней посты об этом будут появляться там — t.me/find_fun_app
....
в этом канале скоро будут посты о болях с точки зрения кода — почему мне пришлось писать свой LazyList, как сделать кастомную анимацию на Compose и как вкатиться в многомодульную архитектуру, если ты скромный проект на пару тысяч строк (и нужно ли это)
У меня появилась боль — когда я хотел потратить свободное время, у меня из головы вылетали все идеи
Начал вести список развлечений — оказалось, что без фильтров это очень неудобно
Решил делать приложения (не зря же умею). А чтобы оно не умерло до релиза, решил обернуть это все в стартап и завести отдельный канал, где рассказываю о трудностях в разработке
Если вам интересно, с какими факапами можно столкнуться при запуске своего проекта (от поиска иконок до оформления документов) и пройти путь от зарождения идеи до релиза (или удаления папки с проектом) со мной, то раз в пару дней посты об этом будут появляться там — t.me/find_fun_app
....
в этом канале скоро будут посты о болях с точки зрения кода — почему мне пришлось писать свой LazyList, как сделать кастомную анимацию на Compose и как вкатиться в многомодульную архитектуру, если ты скромный проект на пару тысяч строк (и нужно ли это)
🔥11👌5❤2👍2
Kotlin — вложенные и внутренние классы
Кроме наследования, есть два типа отношений между классами:
• вложенные (nested)
class A {
class B
}
создать экземпляр класса B: A.B()
зачем: выделить часть функционала класса A, используемый только с классом A, в отдельный класс B
• внутренние (inner)
class A {
inner class B
}
создать экземпляр класса B: A().B()
зачем: аналогично вложенному, но при этом функционал, выделенный в класс B, зависит от полей класса A
Отличия:
вложенный класс не связан с экземпляром внешнего — они создаются отдельно
внутренние классы содержат ссылку на внешний класс, то есть во внутренний класс неявно передается ссылка на внешний класс
у внутреннего класса есть доступ к public, private и protected полям внешнего класса
Возможная опасность:
передавая куда-то внутренний класс, нужно помнить, что он содержит ссылку на внешний. это может привести к утечки памяти
....
есть примеры из Android SDK, библиотек или личного опыта, когда использование вложенных или внутренних классов было действительно оправдано?
Кроме наследования, есть два типа отношений между классами:
• вложенные (nested)
class A {
class B
}
создать экземпляр класса B: A.B()
зачем: выделить часть функционала класса A, используемый только с классом A, в отдельный класс B
• внутренние (inner)
class A {
inner class B
}
создать экземпляр класса B: A().B()
зачем: аналогично вложенному, но при этом функционал, выделенный в класс B, зависит от полей класса A
Отличия:
вложенный класс не связан с экземпляром внешнего — они создаются отдельно
внутренние классы содержат ссылку на внешний класс, то есть во внутренний класс неявно передается ссылка на внешний класс
у внутреннего класса есть доступ к public, private и protected полям внешнего класса
Возможная опасность:
передавая куда-то внутренний класс, нужно помнить, что он содержит ссылку на внешний. это может привести к утечки памяти
....
есть примеры из Android SDK, библиотек или личного опыта, когда использование вложенных или внутренних классов было действительно оправдано?
👍21🔥3❤1
Почему нельзя писать liveData.observe(this...) во Fragment?
Lifecycle — объект, который отслеживает жизненный цикл (Fragment, Activity....) и передает их в качестве состояния тем, кто на этот жизненный цикл подписался (ViewModel, LiveData...)
LifecycleOwner — интерфейс, который содержит единственный метод — getLifecycle() : Lifecycle
LiveData.observe(...) — может принимать ссылку и на Lifecycle, и на LifecycleOwner
С помощью объекта Lifecycle мы сообщаем LiveData, когда данные должны приходить подписчику, а когда — уже нет
Activity и Fragment реализует интерфейс LifecycleOwner
Получается, что в Activity и Fragment мы спокойно можем просто передавать this в LiveData.observe()?
В Activity можем, а во Fragment — можем, но так делать не стоит
Дело в том, что у Fragment жизненный цикл View и самого фрагмента не жестко связаны:
если фрагмент был убран с экрана (onDetach), но добавлен в backstack, то View может быть уничтожена (onDestroyView), а сам фрагмент — нет
получается, если при подписки на LiveData мы передадим ссылку на фрагмент, а не на viewLifecycleOwner, то возможна ситуация, когда данные придут, а View уже уничтожено — приложение упадет
Lifecycle — объект, который отслеживает жизненный цикл (Fragment, Activity....) и передает их в качестве состояния тем, кто на этот жизненный цикл подписался (ViewModel, LiveData...)
LifecycleOwner — интерфейс, который содержит единственный метод — getLifecycle() : Lifecycle
LiveData.observe(...) — может принимать ссылку и на Lifecycle, и на LifecycleOwner
С помощью объекта Lifecycle мы сообщаем LiveData, когда данные должны приходить подписчику, а когда — уже нет
Activity и Fragment реализует интерфейс LifecycleOwner
Получается, что в Activity и Fragment мы спокойно можем просто передавать this в LiveData.observe()?
В Activity можем, а во Fragment — можем, но так делать не стоит
Дело в том, что у Fragment жизненный цикл View и самого фрагмента не жестко связаны:
если фрагмент был убран с экрана (onDetach), но добавлен в backstack, то View может быть уничтожена (onDestroyView), а сам фрагмент — нет
получается, если при подписки на LiveData мы передадим ссылку на фрагмент, а не на viewLifecycleOwner, то возможна ситуация, когда данные придут, а View уже уничтожено — приложение упадет
👍33❤3
Многопоточность за 100 слов
Проблема:
есть объект
запускаем два потока
у каждого потока свой кэш, то есть каждый поток создает копию этого объекта, а не работает с ним напрямую
дальше по шагам:
1) первый поток скопировал объект к себе в кэш
2) первый поток изменил поле объекта
3) второй поток скопировал объект к себе в кэш
4) второй поток изменяет это же поле (и не знает об изменениях, сделанных первым потоком!)
5) второй поток записывает изменения в общую память
6) первый поток записывает изменения в общую память
бац — мы потеряли изменения, сделанные во втором потоке. первый поток их перезаписал + второй поток даже не знал, что объект был изменен в первом потоке!
Как этого избежать:
• synchronized — запрещает нескольким потокам работать с объектом (классом) одновременно
перед тем как выполнить участок кода, поток захватывает объект (класс), а другие потоки ждут освобождения объекта (класса)
- synchronized(Object) — захватывает объект
- synchronized fun — можно читать как synchronized(this), захватывает объект
- static synchronized fun — захватывает класс (а не объект)
• volatile — синхронизирует чтение и запись
сработает, если один поток только читает, а другой только пишет
не сработает, если оба потока изменяют объект — потому что операции между чтением и записью не синхронизированы
• atomic
атомарная операция = неделимая операция изменения объекта
с объектами, имеющими атомарные операции изменения (например, увеличение на единицу для AtomicInteger), можно работать в разных потоках без ручной синхронизации
в Java есть атомарные примитивы, коллекции и класс AtomicReference для работы с произвольными классами
....
а нужно ли думать о синхронизации переменной в корутинах?
сработают ли там эти подходы?
Проблема:
есть объект
запускаем два потока
у каждого потока свой кэш, то есть каждый поток создает копию этого объекта, а не работает с ним напрямую
дальше по шагам:
1) первый поток скопировал объект к себе в кэш
2) первый поток изменил поле объекта
3) второй поток скопировал объект к себе в кэш
4) второй поток изменяет это же поле (и не знает об изменениях, сделанных первым потоком!)
5) второй поток записывает изменения в общую память
6) первый поток записывает изменения в общую память
бац — мы потеряли изменения, сделанные во втором потоке. первый поток их перезаписал + второй поток даже не знал, что объект был изменен в первом потоке!
Как этого избежать:
• synchronized — запрещает нескольким потокам работать с объектом (классом) одновременно
перед тем как выполнить участок кода, поток захватывает объект (класс), а другие потоки ждут освобождения объекта (класса)
- synchronized(Object) — захватывает объект
- synchronized fun — можно читать как synchronized(this), захватывает объект
- static synchronized fun — захватывает класс (а не объект)
• volatile — синхронизирует чтение и запись
сработает, если один поток только читает, а другой только пишет
не сработает, если оба потока изменяют объект — потому что операции между чтением и записью не синхронизированы
• atomic
атомарная операция = неделимая операция изменения объекта
с объектами, имеющими атомарные операции изменения (например, увеличение на единицу для AtomicInteger), можно работать в разных потоках без ручной синхронизации
в Java есть атомарные примитивы, коллекции и класс AtomicReference для работы с произвольными классами
....
а нужно ли думать о синхронизации переменной в корутинах?
сработают ли там эти подходы?
🔥26👍17❤4🐳1
с нг! 🍻🍻🍻
желаю каждому вкусно покушать, порадоваться жизни и всего хо-ро-ше-го!
....
по поводу канала — постов нет не потому что их нет, а потому что копим ману на следующий год
как говорил один разоблачитель клоунов: "Ведь если я способен вернуться — способен каждый!"
желаю каждому вкусно покушать, порадоваться жизни и всего хо-ро-ше-го!
....
по поводу канала — постов нет не потому что их нет, а потому что копим ману на следующий год
как говорил один разоблачитель клоунов: "Ведь если я способен вернуться — способен каждый!"
🎉34👍3🍾3🎄3
Density vs Resolution vs dp vs sp vs dpi
Разбираемся, чем displayMetrics.density отличается от displayMetrics.densityDpi
и фиксируем, когда 1 dp == 1 px
читать
Разбираемся, чем displayMetrics.density отличается от displayMetrics.densityDpi
и фиксируем, когда 1 dp == 1 px
читать
Telegraph
Density vs Resolution vs dp vs sp vs dpi
Screen Resolution == Разрешение экрана количество пикселей например, 320х680 Screen Density == dpi (dots per inch) == ppi (pixels per inch) == Количество пикселей/точек на дюйм == Плотность == resources.displayMetrics.densityDpi обычно мы создаем ресурсы…
👍18❤2🐳2
наша постоянная рубрика — "Вечер рекомендаций"
всё что ниже — не реклама, а просто делюсь годнотой
....
• сообщество Coffee&Code — мобильные разработчики собираются оффлайн (!) каждую неделю
обсуждают айтишные подкасты про смузи, меряются количеством килока в секунду, а потом едут курить кальяны
короче, супер круто, всем советую
вот московский чатик. через него можно выйти на остальные города
иногда сам туда заезжаю, да и в целом там куча крутых мужиков и девчонок, а также разных селебрити)
• канал об Android-разработке — Android Easy Notes
тут сказать особо нечего, просто клево, интересно и не душно. и темы прикольные
....
если тоже знаете что-то крутое(или сами делаете крутое) , айда делиться ссылками в комментариях)
всё что ниже — не реклама, а просто делюсь годнотой
....
• сообщество Coffee&Code — мобильные разработчики собираются оффлайн (!) каждую неделю
обсуждают айтишные подкасты про смузи, меряются количеством килока в секунду, а потом едут курить кальяны
короче, супер круто, всем советую
вот московский чатик. через него можно выйти на остальные города
иногда сам туда заезжаю, да и в целом там куча крутых мужиков и девчонок, а также разных селебрити)
• канал об Android-разработке — Android Easy Notes
тут сказать особо нечего, просто клево, интересно и не душно. и темы прикольные
....
если тоже знаете что-то крутое
❤7🤓6🥰3🤡3👍2🔥2
Удобнее читать посты в
Anonymous Poll
49%
голой телеге (как было в том году)
60%
как отдельные статьи в Telegraph (последние два поста)
Animation — сколько нужно классов, чтобы красиво переместить вьюшку?
Глоссарий:
• интерполятор — функция, описывающая изменение значения во времени
например, значение может изменяться линейно или по экспоненте
• pivotX, pivotY — координата точки внутри View, относительно которой будет применена анимация (например, вращение)
Как можно сделать анимацию:
• ValueAnimator — задаем начальное и конечное значение числа, назначаем интерполятор, вешаем колбек, получаем значение каждые n миллисекунд, делаем с ним что хотим
• ObjectAnimator — то же самое, что и ValueAnimator, только сразу принимает название параметра View, который нужно менять
например, ObjectAnimator.ofFloat(view, "alpha", 1f, 0f)
• ViewPropertyAnimator — оптимизированный ObjectAnimator
имеет более удобный синтаксис, синхронизирует внутри себя анимации над одним и тем же параметром
• LayoutTransition — определяет анимацию появления/удаления элемента
например, есть дефолтная реализация для LinearLayout, включается параметром android:animateLayoutChanges="true"
• Scene — анимация между состояниями экрана
создается две xml — со стартовым и конечным положением вьюшек
• MotionLayout — наследник ConstraintLayout с API для анимации сложных перемещений вьюшек, с которыми взаимодействует пользователь
• WindowAnimation — анимашки перехода между Activity
• AnimationDrawable — описание анимации в drawable
Дополнительное:
• AnimatorSet — упрощает комбинацию анимашек
например, можно запустить анимации последовательно или параллельно с некоторой задержкой
• Fling Animation — придает "физичность" спискам
когда мы быстро свайпаем список, а потом отрываем палец, список сначала быстро разгоняется, а потом медленно останавливается, как будто есть сопротивление воздуха
• Spring animation — придает "физичность" смахиваемым элементам
когда смахиваешь письмо в некоторых почтовых клиентах, оно немного пружинит, как будто отталкивается от соударения с краем экрана
• LottieAnimation — иногда дизайнерам легче скинуть готовый Lottie-файл с анимацией. нужна сторонняя библиотека
....
как дела с анимацией в Compose, все потребности уже закрыты?
Глоссарий:
• интерполятор — функция, описывающая изменение значения во времени
например, значение может изменяться линейно или по экспоненте
• pivotX, pivotY — координата точки внутри View, относительно которой будет применена анимация (например, вращение)
Как можно сделать анимацию:
• ValueAnimator — задаем начальное и конечное значение числа, назначаем интерполятор, вешаем колбек, получаем значение каждые n миллисекунд, делаем с ним что хотим
• ObjectAnimator — то же самое, что и ValueAnimator, только сразу принимает название параметра View, который нужно менять
например, ObjectAnimator.ofFloat(view, "alpha", 1f, 0f)
• ViewPropertyAnimator — оптимизированный ObjectAnimator
имеет более удобный синтаксис, синхронизирует внутри себя анимации над одним и тем же параметром
• LayoutTransition — определяет анимацию появления/удаления элемента
например, есть дефолтная реализация для LinearLayout, включается параметром android:animateLayoutChanges="true"
• Scene — анимация между состояниями экрана
создается две xml — со стартовым и конечным положением вьюшек
• MotionLayout — наследник ConstraintLayout с API для анимации сложных перемещений вьюшек, с которыми взаимодействует пользователь
• WindowAnimation — анимашки перехода между Activity
• AnimationDrawable — описание анимации в drawable
Дополнительное:
• AnimatorSet — упрощает комбинацию анимашек
например, можно запустить анимации последовательно или параллельно с некоторой задержкой
• Fling Animation — придает "физичность" спискам
когда мы быстро свайпаем список, а потом отрываем палец, список сначала быстро разгоняется, а потом медленно останавливается, как будто есть сопротивление воздуха
• Spring animation — придает "физичность" смахиваемым элементам
когда смахиваешь письмо в некоторых почтовых клиентах, оно немного пружинит, как будто отталкивается от соударения с краем экрана
• LottieAnimation — иногда дизайнерам легче скинуть готовый Lottie-файл с анимацией. нужна сторонняя библиотека
....
как дела с анимацией в Compose, все потребности уже закрыты?
👍14❤3🕊3❤🔥1
Много внешних библиотек
После появления более трех модулей в приложении становится сложновато отслеживать, какие либы и какой версии были подключены
Существует несколько способов их упорядочить
читать
После появления более трех модулей в приложении становится сложновато отслеживать, какие либы и какой версии были подключены
Существует несколько способов их упорядочить
читать
Telegraph
Много внешних библиотек
После появления более трех модулей в приложении становится сложновато отслеживать, какие либы и какой версии были подключены Существует несколько способов их упорядочить: • общий модуль с зависимостями подключение библиотек обычно осуществляется через ключевое…
🙏11👍8🔥4🎉1