Mobile VK Hub


Гео и язык канала: Россия, Русский
Категория: Технологии


Комьюнити от VK для мобильных разработчиков. Здесь всё о том, как создаются приложения для миллионов: от нативных подходов до кросс-платформы.

Связанные каналы

Гео и язык канала
Россия, Русский
Категория
Технологии
Статистика
Фильтр публикаций


iOS 27 SDK: что сломается при пересборке на Xcode 27

Каждую осень новый SDK приносит список изменений, и большая их часть проявляется, только когда проект пересобирают на свежем Xcode. С 9 сентября App Store принимает сборки на Xcode 27, а с апреля 2027 года будет принимать только сборки на SDK iOS 27 и новее.

В этом году список интереснее. Приложение без scene-based жизненного цикла, собранное с iOS 27 SDK, просто не запустится, launch screen стал обязательным для ревью, а из Xcode убрали старый линкер. Часть изменений не даёт ошибок сборки и всплывает уже в интерфейсе.

➡️ В карточках разберём scene-based жизненный цикл, launch screen, линкер и требования Xcode 27 к CI, замену canOpenURL, новый @State и поведение UIKit, которое меняется без предупреждений.

#mobilevkhub #ios #xcode


Главное в мобильной разработке в сентябре 2026 года — осенние релизы Apple. iOS 27, Xcode 27 и Swift 6.4 вышли на одной неделе и привнесли требования, из-за которых старые проекты ломаются при пересборке. У Android вышел стабильный Navigation3 1.2, Compose начал уходить от текстовых полей на value, а Kotlin показал первую бету 2.5.

🟣iOS 27 и Xcode 27: scene-based жизненный цикл обязателен

С 9 сентября App Store принимает сборки на Xcode 27, а 14 сентября вышли сами iOS 27 и Xcode 27. Приложение, собранное с новым SDK, не запустится без scene-based жизненного цикла, а без launch screen в Info.plist его отклонит ревью. Из Xcode убрали линкер ld64 вместе с флагом -ld_classic, PreviewProvider помечен устаревшим. С апреля 2027 года App Store будет принимать только сборки на SDK iOS 27 и новее.

🟣SwiftUI: новый @State работает и на iOS 17

@State стал макросом и больше не вызывает инициализатор модели при каждом пересоздании вью. Согласно release notes, новое поведение работает на всех системах начиная с iOS 17, если проект собран в Xcode 27. Код, где значение задано в объявлении и ещё раз присваивается в init, частично перестал компилироваться.

🟣Swift 6.4: await в defer

Релиз от 15 сентября разрешил await внутри defer и добавил withTaskCancellationShield для очистки, которую не должна прерывать отмена задачи. Swift Build стал сборщиком по умолчанию в SwiftPM, а проверки двух тестовых фреймворков стали взаимозаменяемы: XCTAssert работает в Swift Testing, #expect — в XCTest.

🟣Kotlin 2.4.20 и 2.5.0-Beta1

В 2.4.20 от 7 сентября Swift export превращает sealed-иерархии в Swift enum, так что switch по ним обходится без default. Swift-классы теперь могут наследовать открытые классы Kotlin. Бета 2.5 от 23 сентября сделала стабильной деструктуризацию по именам и добавила экспериментальные блоки companion {}.

🟣Navigation3 1.2.0

В стабильной версии от 23 сентября появились передача результатов между экранами через ResultEventBus, мультиплатформенные диплинки с UriDeepLinkMatcher и NavigationBackHandler для predictive back.

🟣Compose: TextFieldState вместо value

В 1.13.0-alpha03 от 9 сентября перегрузки BasicTextField и TextField из Material 2 с параметрами value и onValueChange помечены устаревшими в пользу TextFieldState. Вместо maxLength появились maxLengthTrim и maxLengthReject, а API Grid и FlexBox вышли из экспериментальных.

🟣Сборка: AGP 9.4

AGP 9.4 требует Gradle 9.6 и JDK 17. В AGP 10 новый Variant API станет обязательным, а модули, которые к нему ещё не готовы, пока можно исключить через android.newDsl.optOut.

🟣Безопасность Android

Сентябрьский бюллетень закрывает критическую CVE-2026-28604: удалённое выполнение кода в ADB без участия пользователя. Все уязвимости из бюллетеня закрывает уровень патча 2026-09-05.

#mobilevkhub #дайджест


@State в iOS 27: Observable-объекты перестали создаваться впустую

Строка @State private var model = DataModel() выглядит безобидно, пока в инициализаторе не появится точка останова. SwiftUI пересоздаёт структуру вью на каждом обновлении родителя, и конструктор вызывается снова и снова. Лишние экземпляры система выбрасывает сразу, оставляя первый, а вот код внутри init успевает отработать целиком. Если там разбор конфигурации, поднятие клиента или чтение с диска, за одну прокрутку списка эта работа уходит в мусор десятки раз.

Обходили по-разному. Кто-то возвращался к @StateObject, ленивому с самого начала, кто-то держал инициализатор пустым и догружал данные в task. Третий путь — вынести объект уровнем выше и прокинуть через окружение.

В SDK iOS 27, вышедшего 14 сентября 2026, @State стал макросом, и классы внутри него инициализируются лениво, ровно один раз за время жизни вью:
@Observable
final class DataModel {
var items: [Item] = []

init() {
// теперь выполнится один раз, а не на каждом обновлении родителя
}
}

struct FeedView: View {
@State private var model = DataModel()

var body: some View {
List(model.items) { ItemRow(item: $0) }
}
}
Код выглядит так же, как год назад, а поведение под ним другое. Объект перестал быть расходником, поэтому тяжёлую подготовку можно держать прямо в инициализаторе, не растаскивая её по onAppear и task.

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

Одно осталось прежним. Ленивость распространяется только на выражение в объявлении, так что, если модель присваивается свойству в init() самого вью, инициализатор по-прежнему выполняется на каждом пересоздании, а присвоение игнорируется, и в свойстве может остаться устаревшее значение.

Перепроверить стоит и участки кода,, где инициализатор модели делал что-то неидемпотентное: писал аналитику, инкрементировал счётчик, регистрировался в синглтоне. Такой код годами работал вместе с лишними вызовами, и после перехода на новый SDK число событий изменится.

Заодно в этом же релизе ViewBuilder стал доступен как ContentBuilder, и сборка сложных вложенных вью в Xcode 27 заметно ускорилась. На скорость самого приложения это не влияет, зато сокращает время компиляции.

Если проект уже собирается новым SDK, включать ленивость отдельно не нужно, она работает сама. А вот привычку выносить дорогую инициализацию в task можно пересмотреть: модель снова имеет право быть тяжёлой.

А вы уже поднимали target на iOS 27?

#mobilevkhub #ios


explicit backing fields: как убрать пару _state и state из каждой ViewModel

В любой ViewModel живёт одна и та же конструкция: приватный MutableStateFlow и публичный StateFlow поверх него. Две строки вместо одной, подчёркивание в имени, о котором когда-то договорились на ревью, и постоянный риск отдать наружу мутабельный тип по невнимательности.
class ProfileViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow = _uiState
}
В Kotlin 2.4 explicit backing fields стали стабильными, и эта пара схлопывается в одно свойство. Публичный тип объявляется как обычно, а ключевое слово field задаёт реальный тип хранения:
class ProfileViewModel : ViewModel() {
val uiState: StateFlow
field = MutableStateFlow(UiState.Loading)

fun retry() {
uiState.value = UiState.Loading // внутри класса это MutableStateFlow
}
}
Дальше всё решает точка обзора. Внутри класса компилятор видит поле и отдаёт MutableStateFlow, поэтому значение меняется привычным .value. Снаружи то же имя имеет тип StateFlow, и присвоить в него не выйдет. Гарантия осталась прежней, а лишнее имя ушло.

С коллекциями приём работает так же, и там он избавляет сразу от двух костылей: не нужен ни toList() на каждом обращении, ни отдельное неизменяемое представление.
class Cart {
val items: List
field = mutableListOf()

fun add(item: String) = items.add(item)
}
Требований к такому свойству немного, но соблюдаются они строго. Оно обязано быть val, без собственного геттера, не open и не делегированное. Тип поля должен быть подтипом типа свойства, а видимость у поля всегда приватная. Для var синтаксис не работает, так что изменяемые свойства остаются на старой схеме.

Снаружи не меняется ничего: collectAsStateWithLifecycle() в Compose и подписки в тестах видят прежний StateFlow. Под ту же схему попадают события через MutableSharedFlow и вообще любые пары, где внутри нужен изменяемый тип, а наружу отдаётся его неизменяемый родитель.

Сам синтаксис появился ещё в 2.3.0 в декабре 2025, но собирался только с флагом -Xexplicit-backing-fields. Теперь опт-ин не нужен, достаточно поднять версию Kotlin в проекте.
Переписывать разом весь модуль смысла мало.

Больше всего выигрывают классы, где таких пар несколько: экранные ViewModel с отдельными потоками для контента, ошибок и прогресса. Там уходит половина объявлений, а вместе с ними и путаница, к какому из двух имён обращаться.

#mobilevkhub #kotlin




Поговорим про AI в Android-разработке

📱 Как автоматизировать ручной разбор крашей и инцидентов и начать реально экономить время своей команды? Как сделать автономную систему для массовой генерации UI-автотестов из независимых ИИ-агентов? Чтобы она:
🟣брала тест-кейс из Allure,
🟣анализировала проект и приложение,
🟣писала UI-тест и Page Objects,
🟣запускала всё на эмуляторе,
🟣проверяла, действительно ли тест способен находить ошибку.

Подробнее можно будет узнать на онлайн-конференции для Android-разработчиков Podlodka Android Crew. Тема недели: как ускорить работу команды с AI и не превратить проект в хаос.

Участвуют спикеры VK: Роман Гергерт, разработчик в Core Auto, и Дмитрий Мовчан, руководитель отдела соцсервисов подразделения разработки в Одноклассниках.

🟣Завтра, 22 сентября, с 10:00 до 11:00 Роман расскажет, как устроена автономная AI-система для массовой генерации UI-тестов в его команде и какие инженерные решения позволили её собрать.
🟣В пятницу, 25 сентября, с 10:00 до 11:00 выступит Дмитрий Мовчан. Вместе с Иваном Луценко (Bereke Bank) и Филиппом Шадриным (RWB) они разберут свои кейсы автоматизации работы с крашами и инцидентами, расскажут, в чём AI действительно экономит время команды, а где надёжнее использовать обычный статический анализ и другие инструменты.

🗓 Подробная программа на сайте конференции.

#mobilevkhub #конференция #android






Android 17: изменения, которые сломают приложение молча

Каждый релиз Android приносит список изменений поведения, и большинство из них не требует ничего делать. Но в семнадцатой версии есть несколько таких, которые не выдают ошибку компиляции и не пишут предупреждение в лог — приложение просто начинает вести себя иначе после поднятия targetSdk до 37.

Часть из них ломает код, который годами работал: рефлексия по приватным полям системных классов, правка static final, чтение SMS сразу после получения. Другая часть меняет то, чего пользователь ждёт от приложения.

Разбираем шесть таких изменений и то, как проверить, задевают ли они вас.

#mobilevkhub #android


📌 2 ноября VK Видео проведет трансляцию ежегодной конференции наших партнеров RuStore Mobile GameDev Conf 2026.

Это крупнейшая в России конференция для мобильной игровой индустрии и премия для лучших игр и приложений года.

В прямом эфире будут:

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

🗓 2 ноября
⏲ 16:00–19:00 МСК

Подробнее о конференции


Dispatchers.IO: почему limitedParallelism не ограничивает то, что вы думаете

Приложение ходит в базу и во внешний API, оба вызова обёрнуты в withContext(Dispatchers.IO). Работает, пока API отвечает быстро. Потом на той стороне что-то ломается, ответы идут по 10 секунд — и вместе с ними встаёт локальная база, которая свободна и вообще ни при чём.

Причина в том, что Dispatchers.IO один на всё приложение. Медленный источник держит потоки ожиданием, остальные задачи стоят за ним в очереди. Разделить их может limitedParallelism, только работает эта функция не так, как обещает название.

Что такое Dispatchers.IO на самом деле

Под капотом — не отдельный пул, а представление общего планировщика, который делят IO и Default. Потоки создаются по мере надобности и гасятся, когда простаивают.

Ограничение у IO — 64 параллельные задачи или число ядер, если их больше; меняется свойством kotlinx.coroutines.io.parallelism. Число — это про задачи, а не про потоки: гарантии, что потоков будет ровно столько, никто не даёт.

У Default картина другая — параллелизм равен числу ядер, минимум два. Логика понятная: он для вычислений, а параллельно их не выполнить больше, чем есть ядер.

limitedParallelism на IO ничего не ограничивает

Название подсказывает, что вызов отрезает долю внутри тех же 64 задач. На IO всё наоборот.
val dbDispatcher  = Dispatchers.IO.limitedParallelism(100)
val apiDispatcher = Dispatchers.IO.limitedParallelism(60)
В документации формулировка прямая: представления, полученные через limitedParallelism, ограничением Dispatchers.IO не связаны. На пике система может держать 64 задачи самого IO плюс 100 плюс 60 параллельно. Каждый вызов заводит себе отдельную квоту сверху, а не отрезает кусок общей.

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

Расплата за удобство — легко наплодить потоков. Пять модулей, каждый со своим limitedParallelism(50), дают на пике 250 параллельных блокирующих задач. В спокойном режиме потоков будет мало: представления делят общий пул, лишние гасятся. А вот пиковую картину придётся считать самому.

На Default всё наоборот

Тот же вызов на Dispatchers.Default ведёт себя противоположно: он именно режет, создавая представление на тех же потоках, которое занимает не больше указанного числа.
val imageDispatcher = Dispatchers.Default.limitedParallelism(4)
Тяжёлая обработка не съест все ядра и не заморозит остальные вычисления. Здесь название функции описывает происходящее честно.

Разница в коде никак не видна: две внешне одинаковые строки делают противоположные вещи, и понять это можно только по тому, от какого диспетчера идёт вызов.

Как этим пользоваться

Заводите отдельный диспетчер на каждый внешний источник. База, сеть, файлы — у каждого своя квота, и медленный источник перестаёт тормозить соседей.
class ApiClient(private val http: HttpClient) {
    private val dispatcher = Dispatchers.IO.limitedParallelism(20)

    suspend fun fetch(url: String) = withContext(dispatcher) {
        http.get(url)
    }
}
Размер квоты стоит соотносить с тем, что на другом конце. Пул соединений к базе на 10 штук делает бессмысленным диспетчер на 100: 90 задач просто встанут в ожидание свободного соединения, занимая потоки впустую. Держите квоту близкой к размеру пула.

И держите в голове сумму. Каждая новая квота на IO добавляется к общему пиковому числу, а не берётся из уже выделенного.

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

#mobilevkhub #kotlin


🟣Compose 1.12: mesh-градиенты и широкий цвет

Август в мобильной разработке прошёл вокруг Compose. Стабильная версия приехала 12 августа вместе с BOM 2026.08.00. Главное в релизе — mesh-градиенты. Обычный градиент тянет цвет вдоль линии или от центра, и дальше двух-трёх оттенков начинает выглядеть грязно: в местах смешивания появляется серость, которой в исходных цветах не было. Mesh устроен иначе — задаётся сетка вершин, у каждой свой цвет, и заливка растекается между ними во все стороны сразу. 

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

Рядом — поддержка широкого цветового охвата. Экраны флагманов давно показывают цвета за пределами sRGB, но Compose работал в старом пространстве и обрезал всё, что туда не помещалось. На практике это выглядело так: дизайнер подобрал насыщенный брендовый оттенок, проверил на своём мониторе, а до пользователя тот доехал блёклым. Теперь цвета и шейдеры уходят на отрисовку как есть.

Grid получил именованные области. Раскладку описываешь словами вместо индексов строк и колонок — подход знаком всем, кто писал CSS Grid, и здесь он работает так же: схема видна прямо в коде, а перестановка блоков сводится к правке пары строк.

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

🟣Цена обновления

Версия 1.12 собирается только под compileSdk 37 и требует AGP не ниже 9.1.1, так что обновление затрагивает не библиотеку, а всю сборку проекта.

Команды со свежим тулчейном отделаются формальностью. Остальным придётся заводить отдельную задачу: AGP 9 выкинул поддержку proguard-android.txt, поменял упаковку нативных библиотек и тянет за собой более новый Gradle. 

Каждый пункт по отдельности решается за полчаса, вместе они складываются в спринтовую историю — и узнать об этом лучше до того, как кто-то поднимет BOM в ветке и уйдёт разбираться с красной сборкой.

🟣Что дальше

26 августа следом вышла 1.13.0-alpha02: ветка следующего релиза уже в работе, и в ней продолжают перебирать экспериментальную Slot Table — внутреннюю структуру, на которой держится состояние композиции.

Material3 при этом остаётся на стабильной 1.4.0, а альфа 1.5 идёт своим темпом и до BOM пока не доехала. Если проект тянет Material3 отдельной зависимостью, версии стоит сверить руками: расхождение с BOM здесь обычное дело.

#mobilevkhub #дайджест



Показано 13 последних публикаций.