Библиотека мобильного разработчика | Android, iOS, Swift, Retrofit, Moshi, Chuck
9.61K subscribers
1.65K photos
80 videos
52 files
4.46K links
Все самое полезное для мобильного разработчика в одном канале.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/b60af5a4

Для обратной связи: @proglibrary_feeedback_bot

РКН: https://gosuslugi.ru/snet/67a4adec1b17b35b6c0d8389
Download Telegram
⚙️ Внедрение доступности (Accessibility) в мобильное приложение

Хотите сделать ваше приложение доступным для всех пользователей? Этот промпт поможет реализовать полноценную поддержку accessibility.

📝 Промпт:

Implement comprehensive accessibility features for a mobile app that includes:

— Add proper content descriptions for all UI elements
— Implement logical focus order and navigation
— Support screen readers (TalkBack/VoiceOver)
— Ensure sufficient color contrast and text sizing
— Add accessibility labels and hints
— Support voice control and switch access
— Test with accessibility services enabled


💡 Расширения:

— Добавьте Implement custom accessibility actions для сложных UI компонентов
— Добавьте Add haptic feedback for key interactions для невизуального взаимодействия
— Добавьте Support dynamic font sizing without breaking layouts для адаптивности текста

🐸 Библиотека мобильного разработчика

#буст #MiddlePath
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
✏️ adb shell dumpsys meminfo – детальный анализ использования памяти приложением

Мощный инструмент для мониторинга использования памяти вашим приложением в реальном времени. Помогает находить утечки памяти и оптимизировать потребление RAM.

📌 Ключевые метрики:

1. PSS (Proportional Set Size):

• Реальная память, занимаемая приложением
• Включает разделяемую память (пропорционально)

2. Private Dirty:

• Память, принадлежащая только вашему приложению
• Не может быть выгружена в swap

3. Java Heap:

• Память, управляемая Dalvik/ART
• Ключевой показатель для поиска утечек

⚡️ Практические сценарии:

Мониторинг в реальном времени:

# Каждые 2 секунды обновлять информацию
watch -n 2 "adb shell dumpsys meminfo com.yourapp.package"


Сравнение состояний:


# До и после выполнения операции
adb shell dumpsys meminfo com.yourapp.package > before.txt
# Выполняем действия в приложении
adb shell dumpsys meminfo com.yourapp.package > after.txt
diff before.txt after.txt


Поиск утечек в Activity:

# Проверить, не остались ли Activity в памяти
adb shell dumpsys meminfo | grep -E "Activity|View|Context"


⚙️ Анализ конкретных компонентов:

Только Java Heap:

adb shell dumpsys meminfo com.yourapp.package | grep -A 10 "Java Heap"


Нативные allocations:

adb shell dumpsys meminfo com.yourapp.package | grep -A 5 "Native Heap"


📊 Пример вывода для анализа:

Applications Memory Usage (in KB)
Uptime: 1234567 Realtime: 1234567

** MEMINFO in pid 1234 [com.yourapp] **
Pss Private Private SwapPss Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 12345 12000 345 100 15000 14000 1000
Dalvik Heap 5678 5600 78 50 6000 5800 200


🔧 Продвинутое использование:

Профилирование конкретной операции:

#!/bin/bash
echo "Память до:" > mem_log.txt
adb shell dumpsys meminfo com.yourapp.package >> mem_log.txt

# Запускаем тестовый сценарий
adb shell am start -n com.yourapp/.TestActivity

echo "Память после:" >> mem_log.txt
adb shell dumpsys meminfo com.yourapp.package >> mem_log.txt
Pro-совет: Используйте с procstats для долгосрочного мониторинга:

bash
adb shell dumpsys procstats --hours 1 com.yourapp.package


Как вы отслеживаете использование памяти в своих приложениях? 💬

🐸 Библиотека мобильного разработчика

#буст #MiddlePath #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
2
⚙️ Превращение меню SwiftUI в мини-панель настроек

Меню в SwiftUI часто используются для быстрых одноразовых команд: выберите пункт, выполните действие — и всё готово. Но что, если вы хотите, чтобы меню функционировало не как простой селектор, а как мини-панель настроек, где пользователи могут последовательно переключать несколько пунктов, прежде чем сделать окончательный выбор? По умолчанию меню закрывается, как только пользователь нажимает на пункт, но с помощью подходящего API это поведение можно изменить.

Переосмысление меню

Обычно при добавлении Menu к метке — например, к значку с тремя точками или кнопке — каждое касание внутри меню выполняет действие и немедленно закрывает меню. Это ожидаемый рабочий процесс «выбрать и закрыть».

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

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

В таких случаях поведение закрытия по умолчанию не является идеальным.

Настройка поведения закрытия

SwiftUI расширяет меню с помощью модификатора menuActionDismissBehavior(_:). Это даёт вам возможность точно контролировать, должно ли касание внутри меню приводить к его закрытию или оставаться открытым.

Модификатор принимает один параметр типа MenuActionDismissBehavior, который представляет собой перечисление, определяемое примерно следующим образом:

public enum MenuActionDismissBehavior {
case automatic // system-default behaviour
case enabled // explicitly force dismissal on each tap
case disabled // keep the menu open after taps
}


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

Menu("Options") {
Button("Toggle A") { /* … */ }
Button("Toggle B") { /* … */ }
Divider()
Button("Done") { /* … */ }
}
.menuActionDismissBehavior(.disabled)


При использовании .disabled меню остаётся открытым после каждого действия, позволяя пользователю выполнить несколько действий, прежде чем решить закрыть его.

Наглядный пример: пакетное переключение

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

struct FeatureToggleMenu: View {
@State private var featureA = false
@State private var featureB = false
@State private var featureC = false

var body: some View {
Menu {
Section {
Toggle("Feature A", isOn: $featureA)
Toggle("Feature B", isOn: $featureB)
Toggle("Feature C", isOn: $featureC)
}
.menuActionDismissBehavior(.disabled)

Button("Apply Changes") {
// commit logic here
}
} label: {
Label("Settings", systemImage: "gearshape")
}
}
}


В этом макете:

🔘 Пользователь открывает «Настройки» и переключает различные функции, не закрывая меню при каждом нажатии переключателя.
🔘 После выбора он нажимает «Применить изменения», а затем может закрыть меню (в зависимости от реализации).
🔘 Вы даже можете вручную принудительно закрыть меню, используя специальную кнопку «Готово».

Заключение

Модификатор menuActionDismissBehavior(_:) — это удобный API для преобразования типичных меню SwiftUI в более надежные мини-интерфейсы для настроек, переключателей и многошаговых рабочих процессов. Продуманное использование этого может привести к более понятному и интуитивно понятному пользовательскому интерфейсу, когда вам нужно больше, чем простое взаимодействие «выбрать и применить».

🐸 Библиотека мобильного разработчика

#буст #MiddlePath #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
1😁1
Организация представлений SwiftUI с помощью ToolbarContent и @ToolbarContentBuilder

По мере роста проектов SwiftUI одна из частых проблем — управление сложными иерархиями представлений. Даже простой экран может быстро превратиться в десятки вложенных модификаторов. После снятия лимита в 10 вложенных представлений стало проще писать глубоко вложенные body, но код стал труднее читать и сопровождать.

🔹 Разбираем крупные реализации body

Когда body растягивается на десятки строк, страдает читаемость. Лучше разбивать большие представления SwiftUI на мелкие подпредставления или выделять повторно используемые части в вычисляемые свойства или функции. Это сохраняет лаконичность и упрощает понимание, тестирование и повторное использование.

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

🔹 Проблема с панелями инструментов

Модификатор .toolbar позволяет создавать кнопки, меню и элементы управления, адаптируемые под разные платформы. Но если элементов становится много, код внутри .toolbar { ... } быстро теряет читаемость.
Перенести логику в вычисляемое свойство нельзя — .toolbar ожидает содержимое, соответствующее ToolbarContent.

🔹 ToolbarContent и @ToolbarContentBuilder

SwiftUI решает это с помощью:

ToolbarContent — протокола для элементов панели инструментов;
@ToolbarContentBuilder — билдера, создающего набор элементов панели.

Объединив их, можно вынести содержимое панели инструментов в отдельный блок, сделав body чище и понятнее.

Пример

struct DemoView: View {
@State private var message = "Hello, world!"

var body: some View {
NavigationStack {
Text(message)
.font(.title2)
.padding()
.navigationTitle("Home")
.toolbar { toolbarContent }
}
}

@ToolbarContentBuilder
private var toolbarContent: some ToolbarContent {
ToolbarItem(placement: .navigationBarLeading) {
Button { message = "Left tapped" } label: {
Label("Left", systemImage: "line.3.horizontal")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button { message = "Right tapped" } label: {
Label("Right", systemImage: "star")
}
}
}
}


🔹 Заключение

Использование @ToolbarContentBuilder для отделения содержимого панели инструментов от основного окна имеет ряд преимуществ:

Улучшена читаемость: Ваш body текст по-прежнему сосредоточен на вёрстке, а логика панели инструментов реализована в другом месте.
Лучшая организация: Группировка элементов панели инструментов в отдельном блоке позволяет с первого взгляда оценить их структуру и расположение.
Масштабируемость: Когда на панели инструментов появляется несколько кнопок, меню или условная логика, поддерживать её в рабочем состоянии становится намного проще.

🐸 Библиотека мобильного разработчика

#PixelPerfect #MiddlePath #Swift
Please open Telegram to view this post
VIEW IN TELEGRAM
2
🌛 Реализация офлайн-режима

Хотите, чтобы ваше приложение работало даже без интернета? Этот промпт поможет реализовать надежный офлайн-режим и синхронизацию данных.

📝 Промпт:

Implement offline-first approach for a mobile app that includes:

— Set up local database (Room/SQLite/CoreData)
— Implement data synchronization strategy
— Handle conflict resolution for concurrent modifications
— Manage offline queue for pending operations
— Add network connectivity detection
— Implement cache management and expiration policies
— Provide user feedback for sync status


💡 Расширения:

— Добавьте Implement reactive UI updates using observables для автоматического обновления интерфейса
— Добавьте Add retry mechanisms with exponential backoff для надежной синхронизации
— Добавьте Support large file caching with storage management для работы с медиа в офлайне

🐸 Библиотека мобильного разработчика

#буст #MiddlePath
Please open Telegram to view this post
VIEW IN TELEGRAM
🥱2🔥1🤔1
Эффект ЭЛТ-экрана в Jetpack Compose

ЭЛТ-мониторы — это размытые края, линии сканирования и лёгкое свечение. Такой эффект можно воспроизвести в Compose с помощью GraphicsLayer, градиентов и размытия.

🔹 Базовый принцип

Мы один раз записываем контент во внеэкранный буфер и многократно перерисовываем его разными слоями.

val graphicsLayer = rememberGraphicsLayer()

Box(Modifier.drawWithContent {
graphicsLayer.record { drawContent() }
}) {
content()
}


Теперь drawLayer(graphicsLayer) можно использовать в любых эффектах.

🔹 Линии сканирования

Создаём повторяющиеся градиенты — вертикальные и горизонтальные:

private fun DrawScope.drawScanLines(alpha: Float, blend: BlendMode) {
val c = Colors.Black.copy(alpha)
drawRect(
brush = Brush.verticalGradient(
0f to c, 0.4f to c, 0.4f to Colors.Transparent, 1f to Colors.Transparent,
tileMode = TileMode.Repeated, endY = 10f
),
blendMode = blend
)
}


Добавляем их поверх слоя:

.drawBehind {
layer {
drawLayer(graphicsLayer)
drawScanLines(alpha = 1f, blend = BlendMode.DstOut)
}
}


DstOut вычитает градиент и создаёт характерный "CRT-срез".

🔹 Размытие и свечение

Для реалистичного свечения рисуем несколько слоёв с разным blur/scale/alpha:

val blurLayers = listOf(
Triple(5.dp, .3f, 1.02f to 1.03f),
Triple(0.dp, .8f, 1f to 1f),
Triple(10.dp, .6f, 1.001f to 1f),
)


Каждый слой:

Box(
Modifier
.matchParentSize()
.blur(blur, BlurredEdgeTreatment.Unbounded)
.graphicsLayer { scaleX = scale.first; scaleY = scale.second; this.alpha = alpha }
.drawBehind {
layer {
drawLayer(graphicsLayer)
drawScanLines(1f, BlendMode.DstOut)
}
}
)


🔹 Дрожание экрана

var shake by remember { mutableStateOf(Offset.Zero) }

LaunchedEffect(Unit) {
while (true) {
shake = Offset(
Random.nextFloat() * Random.nextInt(-1, 1),
Random.nextFloat() * Random.nextInt(-1, 1),
)
delay(32)
}
}


И применяем:

.graphicsLayer {
translationX = shake.x
translationY = shake.y
}


🐸 Библиотека мобильного разработчика

#PixelPerfect #MiddlePath #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
🎮 Почему ваша ViewModel технически нестабильна и почему Compose не возражает

А вы знали, что почти все ViewModels нестабильны?

Когда мы только изучаем Compose,
нас учат использовать стабильный класс, а не нестабильный.

Но ViewModels нестабильны. Так почему же никто ничего не говорит о том, что мы используем нестабильные ViewModels?

🔹 Как Compose определяет стабильность?

Компилятор Compose считает класс стабильным, если:

🔘 Все его свойства (val/var) неизменяемы (val).

🔘 Или их тип «отслеживается» рантаймом Compose (например, MutableState<T>).

Взгляните на примеры:

// Стабильный класс
data class Stable(
val a: Int, // Стабильно
val b: MutableState<Int>, // Стабильно, отслеживается Compose
val c: SnapshotStateList<Int> // Стабильно, отслеживается Compose
)

// Нестабильный класс
data class Unstable(
var b: Int // Нестабильно из-за `var`
)

// "Неопределенная" стабильность
data class Runtime(
val i: Interface // Компилятор не знает, какая реализация будет на runtime.
)


Но есть важный нюанс: это правило работает только внутри модуля, где подключен компилятор Compose.

🔹 Что происходит на границах модулей?

Допустим, вы создали стабильную data class в слое данных (data) и внедрили её в ViewModel в слое презентации.

Логично ожидать, что ViewModel тоже будет стабильным. Но на практике — нет!

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

А раз наш ViewModel зависит от репозиториев и UseCase из других модулей (domain/data), то и он сам автоматически становится нестабильным.

🔹 Так почему же нестабильный ViewModel — это норма?

Ответ простой и лежит на поверхности: мы не передаем сам ViewModel в дочерние композаблы.

Вместо этого мы:

1. Создаем ViewModel один раз наверху (например, в NavGraph).

2. Коллектим его состояние (state), которое уже является стабильным.

3. Пробрасываем это стабильное состояние вниз по дереву композиции.

@Composable
fun Screen(viewModel: TestViewModel) { // ViewModel нестабилен, и это ок
val state by viewModel.state.collectAsState() // Состояние - стабильно

Child(state) // Передаем стабильный state
}

@Composable
fun Child(state: TestState) { // Стабильный пропс -> рекомпозиции оптимизированы
Text(state.data)
}


Compose-рантайм следит за изменениями в state. Сам ViewModel как объект не «пробрасывается» глубже и не триггерит лишних рекомпозиций.

🔹 Итоги

🔘 Для монолитных проектов: Это не проблема, компилятор видит все классы и корректно определяет стабильность.

🔘 Для многомодульных проектов: ViewModel почти всегда будет нестабильным из-за зависимостей из других модулей. И это нормально и безопасно, если вы передаете в композаблы не его самого, а его состояние.

Так что можете спать спокойно — с вашим кодом всё в порядке.

🐸 Библиотека мобильного разработчика

#АрхитектурныйКод #MiddlePath #Kotlin
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤‍🔥1🔥1🤔1
✏️ adb shell am broadcast – отправка broadcast-интентов из командной строки

Мощный инструмент для тестирования обработки broadcast-сообщений в вашем приложении без необходимости эмулировать системные события через UI.

🔥 Базовое использование:

# Отправка кастомного broadcast
adb shell am broadcast -a "com.yourapp.action.CUSTOM_ACTION"

# С дополнительными данными
adb shell am broadcast -a "com.yourapp.action.CUSTOM_ACTION" --es "key" "value"


📌 Практические сценарии:

1. Тестирование системных событий:

# Имитация подключения зарядки
adb shell am broadcast -a android.intent.action.ACTION_POWER_CONNECTED

# Имитация изменения языка системы
adb shell am broadcast -a android.intent.action.LOCALE_CHANGED


2. Тестирование кастомных broadcast receivers:

# Отправка с дополнительными extras
adb shell am broadcast -a "com.yourapp.action.DATA_REFRESH" \
--es "refresh_type" "full" \
--ei "user_id" 12345 \
--ez "force_update" true


3. Отправка конкретному пакету:

# Только для вашего приложения
adb shell am broadcast -a "com.yourapp.action.TEST" -n com.yourapp.package/.ReceiverName


⚡️ Полезные системные действия:

Уведомление о низком заряде:

adb shell am broadcast -a android.intent.action.BATTERY_LOW


Изменение конфигурации:

adb shell am broadcast -a android.intent.action.CONFIGURATION_CHANGED


События времени:

adb shell am broadcast -a android.intent.action.TIME_SET


Какие broadcast-ы вы чаще всего тестируете через командную строку? 💬

🐸 Библиотека мобильного разработчика

#буст #MiddlePath #Android
Please open Telegram to view this post
VIEW IN TELEGRAM
🤝1