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

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

Регистрация в перечне РКН: https://tprg.ru/oVBP
Download Telegram
Размер 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.

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

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

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

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

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

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

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

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

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

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

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

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

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

В релизе React Native 0.88 поддержаны строковая CSS-форма и объект. У объекта ключи — четырёхсимвольные теги осей, значения — конечные числа. Ось должна существовать в выбранном шрифте: настройка не добавляет её в файл.
👏2
Новый путь событий в 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.
🔥2
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 включительно. Удаление старых типов в следующем релизе они обозначили как план.
❤1👍1
Выбрать фото и загрузить его — два разных шага

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

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

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

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

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

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

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

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

Если в React Native слушать только событие change, открытие шторки уведомлений на Android останется незамеченным. В этом примере из документации AppState не меняется, но приходит отдельное событие blur: пользователь больше не взаимодействует с приложением.

Для возвращения фокуса есть событие focus. Оба события относятся к Android. Перед использованием проверьте поддержку этих событий в версии React Native вашего проекта. Их можно обрабатывать отдельно от перехода в фон, например, чтобы при потере фокуса приостановить анимацию. Решение о паузе принимает само приложение, автоматически AppState ничего не останавливает.

Событие blur не сообщает, какая именно системная панель открыта. Шторка — пример из справочника AppState, а не способ распознать её. На схеме показаны два разных сигнала: состояние приложения и взаимодействие.
👍2
Как URLSession возвращает события после перезапуска iOS-приложения

Фоновая URLSession передаёт загрузку файла отдельному системному процессу. Если iOS завершила приложение, система может запустить его снова для обработки событий передачи.

В обработчике UIApplicationDelegate сначала сохраняют completionHandler, затем восстанавливают сессию с тем же identifier и делегатом. После urlSessionDidFinishEvents и обработки доставленных событий сохранённый handler вызывают на главном потоке. Этот callback говорит о доставке накопленных событий, а не о завершении всех будущих задач.

При force quit через экран многозадачности iOS отменяет фоновые передачи и не перезапускает приложение автоматически. Это отличается от завершения процесса системой.

Руководство Apple разбирает настройку и восстановление сессии. Метод background(withIdentifier:) доступен с iOS 8.
👍2
This media is not supported in your browser
VIEW IN TELEGRAM
Независимые Flutter-экраны с общими ресурсами движка

При постепенной миграции приложения маршрут может чередовать нативные и Flutter-экраны. На Android и iOS для нескольких экземпляров Flutter есть FlutterEngineGroup. Созданные им движки разделяют GPU-контекст, метрики шрифтов и snapshot группы изолятов.

У каждого движка остаются собственные Dart-программа, интерфейс, навигация и состояние. Для обмена данными между экземплярами документация предлагает platform channels или Pigeon через нативное приложение.

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

В ролике из руководства Flutter показан пример с нативными и Flutter-экранами, включая две Flutter-области одновременно. Это демонстрация интерфейса, без замеров производительности.

Перед использованием сверьте поддержку API с версией Flutter в проекте.
🔥1