Мобильная разработка
13K subscribers
1.04K photos
246 videos
3.18K links
Актуальное по мобильной разработке — Android, iOS, кроссплатформа

Разместить рекламу: @tproger_sales_bot
Правила общения: https://tprg.ru/rules
Другие каналы: @tproger_channels
Сайт: https://tprg.ru/site

Регистрация в перечне РКН: https://tprg.ru/oVBP
Download Telegram
This media is not supported in your browser
VIEW IN TELEGRAM
Перетащить изображение в Compose — значит передать данные

В официальном демо изображение перетаскивают из одной карточки в другую внутри приложения. При этом меняется содержимое приёмника. Перестановка элементов списка — другая задача.

Источник отмечают dragAndDropSource, приёмник — dragAndDropTarget. Данные передаются через DragAndDropTransferData с ClipData. На входе можно отфильтровать MIME-типы и принимать только нужный формат.

В руководстве Android разобран и обмен между приложениями: для выхода за границы приложения нужен подходящий флаг, например DRAG_FLAG_GLOBAL. При обработке внешнего URI запрашивают drag-and-drop permissions через Android-событие и освобождают их после работы с данными.
SwiftUI: страница и карточка задают разные точки остановки

В ленте с карточками разной ширины можно привязать остановку к отдельной карточке. В SwiftUI точку остановки определяет геометрия контейнера или элемента — в зависимости от выбранного режима.

.paging ориентируется на размер контейнера прокрутки. .viewAligned привязывает остановку к отдельному элементу. Если карточки лежат в ленивом контейнере, его отмечают .scrollTargetLayout(), чтобы ScrollView видел цели внутри. На схеме показана эта разница, а не результат замера.

API доступен с iOS 17, iPadOS 17 и macOS 14. В документации Apple есть оба режима.
🔥1
Переход к далёкой строке FlatList через getItemLayout

При переходе к сотой строке нужный элемент может ещё не попасть в окно рендера. Для такого вызова scrollToIndex документация React Native требует getItemLayout.

Если высота строки заранее известна, getItemLayout возвращает её размер, индекс и смещение без динамического измерения. Разделитель тоже входит в смещение. Для одинаковых строк без шапки и внешних отступов это index × (height + separator), а не просто произведение индекса на высоту.

В документации FlatList 0.87 разобраны эти условия. Для карточек с текстом переменной длины нельзя подставлять одну условную высоту: переход приведёт не к той позиции. Схема относится только к строкам фиксированного размера.
❤1👍1👎1
Повторяющиеся изображения можно загружать через общий pipeline

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

ImagePipeline отвечает за асинхронную загрузку и прогресс, а LazyImage из NukeUI — за отображение в SwiftUI. Совпадения URL недостаточно, чтобы гарантировать одну сетевую операцию: объединение запросов зависит от параметров и этапов обработки.

Для релизной Nuke 13 указаны Swift 6.2, Xcode 26 и iOS 15. Ветка main уже посвящена Nuke 14 в разработке, её требования нельзя автоматически переносить на релиз 13. Схема показывает роли компонентов, без сравнения скорости загрузчиков.
Первое значение Flow ещё не проверяет весь поток

Тест может получить ожидаемое значение и пропустить лишнее событие или ошибку завершения. В Turbine значения и завершение читаются отдельно: через awaitItem(), awaitComplete() и awaitError().

У test после блока проверок отменяется сбор потока и проверяются непрочитанные события. Поэтому конечную последовательность проверяют до завершения, а остаток бесконечного потока игнорируют явно. У testIn нет автоматической отмены корутины: нужны backgroundScope либо явное завершение или отмена.

В README Turbine есть примеры обоих режимов. Таймаут ожидания по умолчанию — три секунды реального времени, виртуальные часы runTest его не ускоряют. Также авторы предупреждают о зависимости от нестабильного UnconfinedTestDispatcher, поэтому совместимость версий coroutines нужно проверять.
This media is not supported in your browser
VIEW IN TELEGRAM
Markdown в SwiftUI с нативным выделением текста

В статье или ответе чата важно сохранить заголовки, ссылки и возможность скопировать текст. Textual сохраняет конвейер SwiftUI Text: InlineText работает со строчным содержимым, StructuredText — с документом из блоков.

Выделение включается через .textual.textSelection(.enabled). Прокручиваемые блоки кода имеют отдельные контексты выделения: выбор внутри блока сбрасывает выделение документа и наоборот. На видео из демо Textual показаны рендеринг и прокрутка текста. Работа выделения в этот фрагмент не вошла.

Textual развивает идеи MarkdownUI. Возможность миграции без изменений не заявлена. Текущий Package.swift требует Swift tools 6.0, iOS 18 и macOS 15. Эти границы стоит проверить до выбора библиотеки для приложения со старыми целевыми ОС.
This media is not supported in your browser
VIEW IN TELEGRAM
У каскадного меню может быть один контроллер анимации

Пункты меню появляются по очереди, но следующий начинает двигаться ещё до того, как остановился предыдущий. В рецепте Flutter анимации пунктов перекрываются, а для кнопки задан свой интервал на общем таймлайне.

Один AnimationController задаёт общий прогресс. Для каждого элемента Interval выделяет участок этого прогресса под отдельную анимацию. Это позволяет задать задержки между стартами без цепочки таймеров. На видео — демонстрация из официального рецепта.

В разборе каскадного меню показаны расчёт интервалов и сборка через AnimatedBuilder. Его параметр child позволяет вынести статичное поддерево из callback, который вызывается на каждом кадре. Длительности подобраны для этого меню и не задают универсальную норму для интерфейсов.
This media is not supported in your browser
VIEW IN TELEGRAM
Перехват Back в корневой Activity отключает анимацию возврата на домашний экран

Если приложение само перехватывает Back в корневой Activity, системная анимация back-to-home отключается. На видео SociaLite возвращается на домашний экран. Это системный переход, а не собственная анимация между Compose-экранами.

На Android 15 и выше системные predictive back-переходы включены по умолчанию у приложений, перешедших на поддерживаемые API обработки Back. Для системной анимации нужна AndroidX Activity 1.6.0+. На Android 13 и 14 для проверки системной анимации нужен переключатель в настройках разработчика.

В инструкции по настройке отдельно разобрана Navigation Compose 2.8.0+ для переходов внутри приложения. При миграции проверьте, где перехватывается Back и куда должен вернуться пользователь: домашний экран, другую Activity или экран в NavHost.
Выбор на графике можно связать с состоянием SwiftUI

Чтобы показать вертикальный маркер под пальцем, не всегда нужен собственный прозрачный overlay с пересчётом координат. Начиная с iOS 17 Swift Charts умеет передавать выбранное значение оси в binding.

В примере Majid модификатор .chartXSelection(value:) обновляет selectedIndex, а RuleMark рисует линию по этому значению. Для диапазона есть binding с ClosedRange. На схеме показана связь состояния и маркера, это не скриншот работающего приложения.

Выбранное значение шкалы само по себе не указывает на ближайшую запись в ваших данных. Если график использует даты, пропуски или нерегулярные точки, сопоставление с моделью нужно продумать отдельно. Статья вышла в 2023 году, приём остаётся полезным для проектов с iOS 17+.
This media is not supported in your browser
VIEW IN TELEGRAM
Шапка списка сдвигает индексы при перестановке карточек

В Compose-библиотеке Reorderable индексы from.index и to.index относятся к элементам LazyColumn. Если перед данными стоит шапка, индекс на экране уже не совпадает с индексом в вашем списке.

В примере с одной шапкой автор вычитает единицу из обоих индексов перед изменением данных. Для нескольких секций это не универсальная формула. Порядок элементов меняется в callback. Для ReorderableItem нужны стабильные ключи.

На видео — демонстрация перестановки из репозитория. Библиотека поддерживает ручки перетаскивания и автопрокрутку у края для ленивых коллекций. У обычных Column и Row автопрокрутки нет. Сохранение нового порядка в хранилище остаётся задачей приложения.
Минута на suite — это минута каждому тесту

Если в Swift Testing назначить набору .timeLimit(.minutes(1)), три теста не будут делить одну минуту на всех. Ограничение применяется к каждому тесту отдельно, включая вложенные наборы.

У параметризованного теста свой лимит получает каждый вызов. Если действуют несколько ограничений, выбирается самое короткое. В официальной документации также указаны минутная гранулярность по умолчанию и возможность задать максимальный лимит для каждого теста на уровне runner. Это не точный секундомер для короткого бенчмарка.

При превышении лимита тест отмечается как неуспешный, а его задача получает отмену. Отмена кооперативная: она не гарантирует принудительную остановку произвольного блокирующего кода. На схеме у трёх тестов отдельные часы, без общего бюджета набора.
This media is not supported in your browser
VIEW IN TELEGRAM
Параллакс во Flutter: сдвиг фона на этапе paint

Карточка движется вместе со списком, а изображение внутри смещается в противоположную сторону. Для расчёта сдвига нужно знать положение карточки относительно окна прокрутки. Эти данные доступны после завершения layout.

В рецепте Flutter фон размещают через Flow. Его FlowDelegate вычисляет положение и трансформацию изображения на этапе paint. Через super(repaint: scrollable.position) делегат подписывается на изменения позиции прокрутки, чтобы перерисовывать фон при движении списка.

Видео показывает официальный пример с семью карточками в SingleChildScrollView и Column. Производительность большой ленты в нём не измеряют. Перенос приёма в длинный список требует отдельной проверки на ваших данных и устройствах.
This media is not supported in your browser
VIEW IN TELEGRAM
Размытой панели нужен правильный источник пикселей

Размыть фон под панелью и размыть содержимое самой панели — разные операции. В Haze 2 для Compose Multiplatform это задаётся явно: HazeInput.Sources берёт содержимое, захваченное hazeSource, а HazeInput.Content — собственное содержимое модификатора.

Базовый модуль haze отвечает за захват и API эффектов, для blur добавляют haze-blur. Все артефакты Haze должны быть одной версии. В видео из репозитория показан macOS-пример Compose Multiplatform, это не запись Android-экрана и не замер производительности.

Haze 2.0 пока в beta. В текущей ветке проверяется Android minCompileSdk 37 — это требование к сборке, а не минимальная версия ОС устройства. Перед переносом в приложение стоит сверить выбранную версию и её требования.
This media is not supported in your browser
VIEW IN TELEGRAM
Сопротивление свайпа и порог открытия — разные настройки

Строка может сдвигаться медленнее пальца, но оставаться открытой уже после небольшого сдвига. В ReanimatedSwipeable за эти ощущения отвечают разные параметры: friction меняет движение панели относительно жеста, а leftThreshold и rightThreshold — пороги открытия.

Направления у действий названы по стороне панели: свайп вправо открывает renderLeftActions, влево — renderRightActions. На видео из документации видно, как кнопки появляются под сдвинутой строкой. Сам жест не подтверждает удаление данных, за действие отвечает callback приложения.

В документации Gesture Handler 3.x есть параметры и методы управления. Для компонента требуется установленный Reanimated и обёртка GestureHandlerRootView.
Почему анализ кадров может остановить превью CameraX

Если анализатор обрабатывает кадры медленнее, чем камера их выдаёт, в CameraX выбор режима backpressure влияет и на привязанное превью.

STRATEGY_KEEP_ONLY_LATEST хранит один свежий кадр для следующей обработки. Новые кадры заменяют его, пока анализатор занят. Увеличение setImageQueueDepth() в этом режиме ничего не меняет.

STRATEGY_BLOCK_PRODUCER позволяет накопить очередь заданной глубины. Когда она заполняется, блокировка затрагивает всю камеру, включая привязанное превью. Если обработать все кадры всё равно невозможно, неблокирующий режим позволяет пропускать их в анализе, сохраняя работу других use case.

Анализ начинается после привязки к lifecycle. Для остановки используют clearAnalyzer() или отвязывают ImageAnalysis.
❤2
TextFieldState хранит больше, чем строку

У редактируемого поля есть текст, выделение и незавершённая композиция ввода. TextFieldState в Compose хранит их вместе и синхронизирует правки из разных источников.

rememberTextFieldState() создаёт объект с сохранением и восстановлением состояния. Переданный initialText нужен при создании. Если после загрузки профиля передать другое начальное значение при рекомпозиции, уже существующее поле не обновится. Для этого нужны методы редактирования самого состояния.

Объект можно держать во ViewModel: он использует snapshot-систему Compose, а зависимостей от UI у него нет. В руководстве по полям ввода показаны edit, работа с буфером и выделением.
🤝1
Проверки доступности можно встроить в Compose UI-тест

Кликабельный элемент без подписи может пройти обычный сценарий нажатия и остаться непонятным для TalkBack. Начиная с Compose 1.8.0 часть таких дефектов проверяет Accessibility Test Framework.

Подключают зависимость ui-test-junit4-accessibility и вызывают enableAccessibilityChecks() у AndroidComposeTestRule. Затем проверки запускает действие в тесте или явный вызов tryPerformAccessibilityChecks. Проверяются в том числе подписи, контраст, размер целей нажатия и порядок обхода.

В примере Android Developers тест падает из-за элемента без читаемой подписи. Эту проверку можно включить в регрессионные тесты экрана. Ручная проверка с TalkBack или Switch Access всё равно нужна: автоматические проверки покрывают лишь часть проблем доступности.
🤝1
Откуда Android берёт список языков приложения

На Android 13 / API 33 и новее язык приложения можно выбрать отдельно от языка системы. Список поддерживаемых языков необязательно вручную вести в XML.

Начиная с Android Studio Giraffe и AGP 8.1, generateLocaleConfig позволяет собрать LocaleConfig из ресурсов приложения и библиотек. Ссылка на него попадёт в итоговый manifest. Нужен compileSdk не ниже 33. Для более старых Android нужны отдельные API и выбор языка внутри приложения.

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

В инструкции по настройке также есть выбор базовой локали. Уже созданный вручную LocaleConfig перед включением генерации нужно убрать, иначе сборка завершится ошибкой.
👍2