Сегодня практически каждый сталкивается с ОС Android, даже заядлые поклонники системы iOS наверняка смотрели на нее. Операционная система Android была разработана для устройств с сенсорным экраном, таких как планшеты и смартфоны. Он основан на ядре Linux от Google и сегодня является одной из самых популярных мобильных операционных систем.
A safer way to collect flows from Android UIs — статья о том, как написать с использованием Kotlin Flow асинхронный код, который не будет страдать от проблем перерасхода ресурсов.
По мнению разработчиков из Google, современный подход к написанию программ для Android выглядит следующим образом: уровень бизнес-логики предоставляет функции приостановки и производителей Flow, а компоненты пользовательского интерфейса вызывают функции приостановки или подписываются на Flow и обновляют пользовательский интерфейс в соответствии с входящими данными.
Выглядеть это все может примерно так. Функция — продюсер обновлений местоположения:
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
result ?: return
try { offer(result.lastLocation) } catch(e: Exception) {}
}
}
requestLocationUpdates(createLocationRequest(), callback, Looper.getMainLooper())
.addOnFailureListener { e ->
close(e) // In case of exception, close the Flow
}
awaitClose {
removeLocationUpdates(callback)
}
}
Часть UI-компонента, подписывающаяся на Flow:
lifecycleScope.launchWhenStarted {
locationProvider.locationFlow().collect {
// Новое местоположение — обновляем UI
}
}
В целом все нормально. Используя lifecycleScope, мы узнали, что код реагирует на изменения в жизненном цикле приложения — когда приложение покидает фон, обработка значений местоположения будет приостановлена. Но! Flow Producer продолжит отправлять данные обновления местоположения.
В этом случае, чтобы избежать этой проблемы, вы можете запускать и останавливать корутину , обработчик Flow самостоятельно при изменении жизненного цикла приложения или использовать lifecycleOwner.addRepeatingJob из библиотеки lifecycle-runtime-ktx версии 2.4.0-alpha01.
lifecycleOwner.addRepeatingJob(Lifecycle.State.STARTED) {
locationProvider.locationFlow().collect {
// Новое местоположение — обновляем UI
}
}
Это почти похоже на предыдущий пример. Однако в этом случае корутина будет полностью остановлена, когда приложение перейдет в состояние, отличное от Lifecycle.State.STARTED, и перезапустится, когда перейдет в это состояние. Продюсер данных о местоположении будет остановлен вместе с ним.
Того же эффекта можно добиться, используя suspend-функцию repeatOnLifecycle
:
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
locationProvider.locationFlow().collect {
// Новое местоположение — обновляем UI
}
}
}
Она удобна в тех случаях, когда перед сбором данных необходимо выполнить определенную работу внутри suspend-функции.
Внутри эта функция использует оператор Flow.
, который можно применять напрямую:
locationProvider.locationFlow()
.flowWithLifecycle(this, Lifecycle.State.STARTED)
.onEach {
// Новое местоположение — обновляем UI
}
.launchIn(lifecycleScope)
По сути, все эти API — это более современная и гибкая замена LiveData.
Noisy Code With Kotlin Scopes — хорошая статья о том, как сделать код на Kotlin чище, понятнее и однозначнее.
Проблема номер 1: let. Многие программисты привыкли использовать let
в качестве простой и удобной альтернативы if (
:
fun deleteImage(){
var imageFile : File ? = ...
imageFile?.let {
if(it.exists()) it.delete()
}
}
Нет смысла это делать. Это плохой способ использовать it, потому что многие из них могут смешаться, если в вашем коде появится другая похожая лямбда. Можете попробовать исправить это с помощью файла imageFile?.let { image ->
, но вы в конечном итоге сделаете еще хуже, так как в той же области появится другая переменная, которая ссылается на то же значение, но с другим именем. И это имя придется придумать!
На самом деле в большинстве случаев исправить эту проблему можно простым отказом от let
:
С этим кодом все в порядке. Умное приведение типов сделает свою работу, и ты сможешь ссылаться на imageFile
после проверки как на не nullable-переменную.
Но! Такой прием не сработает, если речь идет не о локальной переменной, а о полях класса. Из‑за того что поля класса могут изменяться несколькими методами, работающими в разных потоках, компилятор не сможет использовать смарткастинг.
Как раз здесь и можно использовать let
.
Но есть и более необычные способы. Например, выходить из функции, если значение поля null:
fun deleteImage() {
val imageFile = getImage() ?: return
...
}
Или использовать метод takeIf
:
fun deleteImage() {
getImage()?.takeIf { it.exists }?.let {it.delete()}
}
Можно даже скомбинировать оба подхода:
fun deleteImage() {
val image = getImage()?.takeIf { it.exists } ?: return
image.delete()
}
Проблема номер 2: also и apply. Синтаксис Kotlin поощряет использование лямбд везде, где только возможно. Иногда это приводит к созданию весьма монструозных конструкций:
Intent(context, MyActivity::class.java).apply {
putExtra("data", 123)
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
}).also { intent ->
startActivity(this@FooActivity, intent)
}
Цель этого кода — ограничить создание и использование интента двумя разными областями видимости, что теоретически должно принести пользу его модульности.
На самом деле такие конструкции только захламляют код. Гораздо красивее выглядит его более прямолинейная версия:
val intent = Intent(context, MyActivity::class.java).apply {
putExtra("data", 123)
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
})
startActivity(this@FooActivity,intent)
А еще лучше вынести код конфигурирования объекта в отдельную функцию:
fun startSomeActivity() {
startActivity(getSomeIntent())
}
fun getSomeIntent() = Intent(context, SomeActivity::class.java).apply {
// ...
}
Проблема номер 3: run. Распространенный прием — использовать функцию run
для обрамления блоков кода:
val userName: String
get() {
preferenceManager.getInstance()?.run {
getName()
} ?: run {
getString(R.string.stranger)
}
}
Это абсолютно бессмысленное захламление кода. Оно затрудняет чтение простого по своей сути кода:
val userName: String
get() = preferenceManager.getInstance()?.getName() ?: getString(R.string.stranger)
Если же кода в блоке «если null» больше одной строчки, то можно использовать такую конструкцию:
preferenceManager.getInstance()?.getName().orDefault {
Log.w(“Name not found, returning default”)
getString(R.string.stranger)
}
Проблема номер 4: with. В данном случае проблема не в самой функции with
, а в ее игнорировании. Разработчики просто не используют эту функцию, несмотря на всю ее красоту:
val binding = MainLayoutBinding.inflate(layoutInflater)
with(binding) {
textName.text = "ch8n"
textTwitter.text = "twitter@ch8n2"
})
Причин не использовать ее обычно две:
Все о PendingIntents — статья, объясняющая, что такое PendingIntent и зачем он вам нужен. Будет полезно в связи с изменениями в обработке PendingIntent в Android 12.
Проще говоря, PendingIntent — это объект-оболочка для Intent, который позволяет вам передать Intent другому приложению для будущего выполнения от имени приложения, создавшего намерение. PendingIntent, в частности, используется, чтобы сообщить системе, что делать при нажатии на уведомление. В этом случае система запускает намерение, указанное в PendingIntent, как если бы оно было запущено приложением, создавшим уведомление.
Простейший пример:
val intent = Intent(applicationContext, MainActivity::class.java).apply {
action = NOTIFICATION_ACTION
data = deepLink
}
val pendingIntent = PendingIntent.getActivity(
applicationContext,
NOTIFICATION_REQUEST_CODE,
intent,
PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(
applicationContext,
NOTIFICATION_CHANNEL
).apply {
// ...
setContentIntent(pendingIntent)
// ...
}.build()
notificationManager.notify(
NOTIFICATION_TAG,
NOTIFICATION_ID,
notification
)
Сначала мы создаем Intent, а затем заключаем его в PendingIntent. Мы используем флаг PendingIntent.FLAG_IMMUTABLE
, чтобы указать, что система или кто-либо другой, у кого есть доступ к этому PendingIntent, не должны его изменять. Этот флаг (или флаг PendingIntent.FLAG_MUTABLE
) является обязательным в Android 12.
При обновлении уведомления мы должны будем указать дополнительный флаг, чтобы заменить старый PendingIntent новым:
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
Почему вам нужно изменение интента внутри PendingIntent? Например, для создания системы подключаемых модулей, в которой подключаемый модуль передает PendingIntent ведущему приложению и изменяет его на основе ввода данных пользователем.
Для изменения PendingIntent используется довольно интересный способ, когда методу send передается другой интент, атрибуты которого становятся частью исходных атрибутов интента:
val intentWithExtrasToFill = Intent().apply {
putExtra(EXTRA_CUSTOMER_MESSAGE, customerMessage)
}
mutablePendingIntent.send(
applicationContext,
PENDING_INTENT_CODE,
intentWithExtrasToFill
)
Ten #AndroidLifeHacks You Can Use Today — сборник маленьких полезных функций для Android-разработчиков. Наиболее интересные:
Функция fadeTo. Вместо View.
лучше применять функцию, которая не просто мгновенно скроет view с экрана, а сделает это с анимацией. Функция fadeTo позволяет сделать это идемпотентно (можно вызывать сколько угодно раз подряд, не сбрасывая анимацию), с указанием продолжительности и конечной прозрачности.
Функция mapDistinct. Простейшая функция — расширение Flow, которая сначала вызывает map
, а затем distinctUntilChanged(
(пропускать одинаковые значения). Код функции представляет собой одну строчку:
Упрощенный Delegates.observable. Функция‑делегат observable
может быть очень удобной, когда необходимо следить за изменениями значения переменной. Но чаще требуется улавливать не все изменения, а только новые, изменившиеся. В этом случае подойдет функция uniqueObservable.
Перезапускаемая Job. При разработке приложений на Kotlin часто требуется завершить предыдущую корутину перед запуском следующей (например, при выполнении поиска или обновлении экрана). Упростить эту процедуру можно с помощью ConflatedJob, которая при запуске завершает предыдущую корутину.
Более удобный Timber. Многие разработчики используют Timber для логирования. Однако делать это напрямую не совсем удобно, приходится писать длинные строки вроде Timber.
. Чтобы облегчить себе жизнь, можно применить такой делегат для более удобной работы с Timber:
Number.dp. При разработке часто требуется преобразовать единицы DP (Density-independent Pixels) в пиксели. Для этого можно использовать такую функцию‑расширение:
Аппаратные средства Android имеют встроенную защиту. В безопасной среде исполнения Trusted Executions Environment (TEE) запускаются все функции безопасности, такие как блокировка экрана или шифрование данных. В то же время, приложения работают в изолированной программно-аппаратной среде, которая ограничивает доступ другому ПО. Другими словами, у вас есть всё для безопасной и продуктивной работы.
Чтобы взломать сеть Wi-Fi с помощью Kali Linux, вам нужна беспроводная карта, поддерживающая режим мониторинга…
Работа с консолью считается более эффективной, чем работа с графическим интерфейсом по нескольким причинам.Во-первых, ввод…
Конечно, вы также можете приобрести подписку на соответствующую услугу, но наличие SSH-доступа к компьютеру с…
С тех пор как ChatGPT вышел на арену, возросла потребность в поддержке чата на базе…
Если вы когда-нибудь окажетесь в ситуации, когда вам нужно взглянуть на спектр беспроводной связи, будь…
Elastic Security стремится превзойти противников в инновациях и обеспечить защиту от новейших технологий злоумышленников. В…