mobi_dev | Unsorted

Telegram-канал mobi_dev - Мобильная разработка

15502

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

Subscribe to a channel

Мобильная разработка

SharePreview убирает ожидание метаданных ссылки

При отправке URL через SwiftUI ShareLink система сначала может показать иконку ссылки и базовый адрес. Заголовок и значок появятся после загрузки метаданных по сети.

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

ShareLink работает с Transferable. Если нужного сервиса нет среди вариантов, проверьте представления данных: часть сервисов принимает файлы. Apple предлагает добавить FileRepresentation к модели.

Оба сценария разобраны в документации ShareLink. API доступен с iOS 16 и iPadOS 16. Немедленное превью не означает, что сами отправляемые данные уже подготовлены.

Читать полностью…

Мобильная разработка

Deep import в React Native стал ошибкой типов

После перехода на React Native 0.87 TypeScript может перестать принимать импорты из react-native/Libraries/*. Strict TypeScript API включён по умолчанию. Он считает публичным API только экспорты из корня react-native. Типы генерируются из исходного кода.

Начать миграцию стоит с внутренних импортов и типов refs. Для refs теперь есть отдельные типы, например ViewInstance и TextInputInstance. В описании 0.87 есть ссылки на руководство по изменениям.

Временный обход — условие react-native-legacy-deep-imports в customConditions файла tsconfig.json. Оно меняет только анализ TypeScript в вашем проекте. Приложение и его библиотеки мигрируют независимо.

В публикации о 0.87 авторы обещали сохранить обход до 0.88 включительно. Удаление старых типов в следующем релизе они обозначили как план.

Читать полностью…

Мобильная разработка

React Native 0.88 добавил управление осями шрифта

Для текста с переменной насыщенностью fontVariationSettings теперь работает в Text и TextInput на Android и iOS.

В релизе React Native 0.88 поддержаны строковая CSS-форма и объект. У объекта ключи — четырёхсимвольные теги осей, значения — конечные числа. Ось должна существовать в выбранном шрифте: настройка не добавляет её в файл.

Читать полностью…

Мобильная разработка

Тяжёлый расчёт конкурирует с отрисовкой кадра

Если CPU-работа не укладывается в промежуток между кадрами, главный isolate Flutter может задержать отрисовку. Такой расчёт можно перенести в отдельный isolate через Isolate.run: он создаёт isolate, возвращает результат и завершает его.

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

UI и rootBundle остаются в главном isolate. Фоновые platform channels принимают ответы на запросы, но не незапрошенные события от платформы. В Flutter web compute выполняется на главном потоке, изоляты там не поддерживаются.

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

Читать полностью…

Мобильная разработка

Размер AAB не равен размеру скачивания

Debug-сборка Flutter содержит накладные расходы для отладки и hot reload. По ней нельзя судить, сколько пользователь скачает из магазина. Размер release-пакета для загрузки в стор тоже не показывает, сколько скачает пользователь.

Магазины разделяют пакет под устройство: учитывают архитектуру процессора и плотность ресурсов. Поэтому в Android размер скачивания и установки смотрят в Play Console после загрузки AAB. В отчёте из документации базовый вариант — XXXHDPI и arm64-v8a, у других устройств размер может отличаться.

Для iOS Xcode создаёт App Thinning Size Report.txt с оценками по вариантам устройств и ОС. Точный отчёт получают после загрузки release IPA в App Store Connect.

Сравнивать оптимизации имеет смысл на одинаковом варианте устройства и отдельно для скачивания и установки.

Читать полностью…

Мобильная разработка

Откуда Android берёт список языков приложения

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

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

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

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

Читать полностью…

Мобильная разработка

TextFieldState хранит больше, чем строку

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

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

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

Читать полностью…

Мобильная разработка

Сопротивление свайпа и порог открытия — разные настройки

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

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

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

Читать полностью…

Мобильная разработка

Параллакс во Flutter: сдвиг фона на этапе paint

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

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

Видео показывает официальный пример с семью карточками в SingleChildScrollView и Column. Производительность большой ленты в нём не измеряют. Перенос приёма в длинный список требует отдельной проверки на ваших данных и устройствах.

Читать полностью…

Мобильная разработка

Шапка списка сдвигает индексы при перестановке карточек

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

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

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

Читать полностью…

Мобильная разработка

Перехват 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.

Читать полностью…

Мобильная разработка

Markdown в SwiftUI с нативным выделением текста

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

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

Textual развивает идеи MarkdownUI. Возможность миграции без изменений не заявлена. Текущий Package.swift требует Swift tools 6.0, iOS 18 и macOS 15. Эти границы стоит проверить до выбора библиотеки для приложения со старыми целевыми ОС.

Читать полностью…

Мобильная разработка

Повторяющиеся изображения можно загружать через общий pipeline

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

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

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

Читать полностью…

Мобильная разработка

SwiftUI: страница и карточка задают разные точки остановки

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

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

API доступен с iOS 17, iPadOS 17 и macOS 14. В документации Apple есть оба режима.

Читать полностью…

Мобильная разработка

Асинхронный MethodChannel ещё не означает работу вне главного потока

В Dart вызов платформенного метода возвращает Future. Это описывает обмен сообщениями, но не выбирает поток для нативного обработчика. Долгая операция в обработчике всё ещё может занять главный поток платформы.

В руководстве Flutter для этого разобран Task Queue API. На Android очередь создают через binaryMessenger.makeBackgroundTaskQueue() и передают в MethodChannel. На iOS Swift-пример получает её через registrar.messenger().makeBackgroundTaskQueue?() и задаёт параметр taskQueue у FlutterMethodChannel.

При этом операции с UI должны оставаться в подходящем потоке платформы. Вызовы канала с нативной стороны в сторону Flutter тоже выполняют на главном потоке. Выбор Task Queue и запуск Dart Isolate — разные настройки. Перед переносом обработчика сверяйте требования плагина и SDK: минимальную версию Task Queue API эта страница не указывает.

Читать полностью…

Мобильная разработка

Выбрать фото и загрузить его — два разных шага

В SwiftUI PhotosPicker возвращает PhotosPickerItem — объект выбора, а не готовое изображение. Содержимое загружают отдельно через loadTransferable.

Загрузка может вернуть изображение, пустое значение или ошибку. Например, фото из iCloud Photos может потребовать сеть, и без соединения получить данные не удастся. Состояние интерфейса должно учитывать эту загрузку после закрытия пикера.

SwiftUI Image поддерживает только PNG и JPEG в своей реализации Transferable. Для других типов изображений Apple предлагает собственную модель Transferable.

Документация PhotosPicker показывает обработку результата и отслеживание прогресса. Сам пикер доступен с iOS 16 и iPadOS 16.

Читать полностью…

Мобильная разработка

Новый путь событий в React Native 0.88

В React Native 0.88 события по умолчанию проходят через W3C EventTarget: сначала идут к цели в фазе capture, затем всплывают. После обновления стоит проверить библиотеки, которые обращаются к внутреннему plugin registry.

Имена event props и их synthetic event objects сохраняются. onLayout по-прежнему срабатывает только на цели. Методы native refs addEventListener, removeEventListener и dispatchEvent в этом релизе доступны только в Canary.

Читать полностью…

Мобильная разработка

Flutter проверяет размер зоны нажатия по правилам платформы

В Flutter Guideline API проверки целей нажатия разделены: androidTapTargetGuideline проверяет минимум 48 × 48, а iOSTapTargetGuideline — 44 × 44.

В widget-тесте сначала включают semantics через ensureSemantics(), создают экран и проверяют нужный guideline. Полученный semantics handle после работы освобождают. В том же тесте можно проверить подписи целей нажатия и контраст через labeledTapTargetGuideline и textContrastGuideline.

В документации Flutter есть полный тест. Такая проверка помогает заметить, например, что после изменения отступов область нажатия иконки стала слишком маленькой. Сценарий взаимодействия отдельно проверяют с VoiceOver или TalkBack.

Читать полностью…

Мобильная разработка

Редко используемый экран можно загрузить позже

Flutter умеет загружать часть кода и ресурсов Android-приложения, когда они понадобились пользователю. Для большого редко используемого раздела можно настроить deferred components.

deferred-импорт позволяет использовать библиотеку после завершения loadLibrary(). Доставку на Android обеспечивают dynamic feature modules. Для Google Play нужны настройка SplitCompat и менеджер deferred components — одного импорта недостаточно.

Отложенная загрузка работает в release и profile, а debug трактует такие компоненты как обычные импорты. При обновлении по-прежнему собирают и загружают целый AAB: отправлять независимые патчи таким способом нельзя.

Документация Flutter разбирает настройку модулей. Выигрыш первого скачивания зависит от того, какие код и ресурсы останутся в базовой части.

Читать полностью…

Мобильная разработка

Видимость элемента не исключает конфликт жестов

На Android 15 / API 35 и новее при target SDK 35 приложение рисует под системными панелями и вырезами экрана. Для важных элементов нужны подходящие отступы.

В Compose есть три безопасных набора WindowInsets. safeDrawing защищает содержимое от визуального перекрытия системным UI. safeGestures помогает избежать конфликта жестов приложения с системными. safeContent объединяет обе защиты.

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

Читать полностью…

Мобильная разработка

Проверки доступности можно встроить в Compose UI-тест

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

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

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

Читать полностью…

Мобильная разработка

Почему анализ кадров может остановить превью CameraX

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

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

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

Анализ начинается после привязки к lifecycle. Для остановки используют clearAnalyzer() или отвязывают ImageAnalysis.

Читать полностью…

Мобильная разработка

Размытой панели нужен правильный источник пикселей

Размыть фон под панелью и размыть содержимое самой панели — разные операции. В 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 — это требование к сборке, а не минимальная версия ОС устройства. Перед переносом в приложение стоит сверить выбранную версию и её требования.

Читать полностью…

Мобильная разработка

Минута на suite — это минута каждому тесту

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

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

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

Читать полностью…

Мобильная разработка

Выбор на графике можно связать с состоянием SwiftUI

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

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

Выбранное значение шкалы само по себе не указывает на ближайшую запись в ваших данных. Если график использует даты, пропуски или нерегулярные точки, сопоставление с моделью нужно продумать отдельно. Статья вышла в 2023 году, приём остаётся полезным для проектов с iOS 17+.

Читать полностью…

Мобильная разработка

У каскадного меню может быть один контроллер анимации

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

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

В разборе каскадного меню показаны расчёт интервалов и сборка через AnimatedBuilder. Его параметр child позволяет вынести статичное поддерево из callback, который вызывается на каждом кадре. Длительности подобраны для этого меню и не задают универсальную норму для интерфейсов.

Читать полностью…

Мобильная разработка

Первое значение Flow ещё не проверяет весь поток

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

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

В README Turbine есть примеры обоих режимов. Таймаут ожидания по умолчанию — три секунды реального времени, виртуальные часы runTest его не ускоряют. Также авторы предупреждают о зависимости от нестабильного UnconfinedTestDispatcher, поэтому совместимость версий coroutines нужно проверять.

Читать полностью…

Мобильная разработка

Переход к далёкой строке FlatList через getItemLayout

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

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

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

Читать полностью…

Мобильная разработка

Перетащить изображение в Compose — значит передать данные

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

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

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

Читать полностью…

Мобильная разработка

ViewThatFits выбирает готовый вариант интерфейса по доступному месту

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

ViewThatFits проверяет дочерние представления по этому порядку и выбирает первое, чей идеальный размер помещается в предложенное пространство по проверяемым осям. По умолчанию учитываются ширина и высота. В примере используется ViewThatFits(in: .horizontal), поэтому решение зависит только от ширины.

Контейнер доступен с iOS 16. Он удобен, когда у компонента есть осмысленные полная и компактная версии. Но эти версии всё равно нужно спроектировать: контейнер лишь выбирает среди них. Крупные размеры Dynamic Type стоит проверить для каждого варианта отдельно.

Читать полностью…
Subscribe to a channel