ar
Feedback
Mobile VK Hub

Mobile VK Hub

الذهاب إلى القناة على Telegram

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

إظهار المزيد
730
المشتركون
+124 ساعات
-17 أيام
-630 أيام

جاري تحميل البيانات...

القنوات المماثلة
لا توجد بيانات
هل تواجه مشاكل؟ يرجى تحديث الصفحة أو الاتصال بمدير الدعم الخاص بنا.
الإشارات الواردة والصادرة
---
---
---
---
---
---
جذب المشتركين
يوليو '26
يوليو '26
+5
في 0 قنوات
يونيو '26
+4
في 1 قنوات
Get PRO
مايو '26
+6
في 1 قنوات
Get PRO
أبريل '26
+9
في 1 قنوات
Get PRO
مارس '26
+9
في 0 قنوات
Get PRO
فبراير '26
+18
في 1 قنوات
Get PRO
يناير '26
+18
في 1 قنوات
Get PRO
ديسمبر '25
+868
في 33 قنوات
Get PRO
نوفمبر '25
+67
في 7 قنوات
التاريخ
نمو المشتركين
الإشارات
القنوات
28 يوليو+1
27 يوليو0
26 يوليو0
25 يوليو0
24 يوليو0
23 يوليو0
22 يوليو0
21 يوليو0
20 يوليو0
19 يوليو0
18 يوليو0
17 يوليو0
16 يوليو0
15 يوليو0
14 يوليو+1
13 يوليو0
12 يوليو0
11 يوليو0
10 يوليو+1
09 يوليو0
08 يوليو0
07 يوليو+2
06 يوليو0
05 يوليو0
04 يوليو0
03 يوليو0
02 يوليو0
01 يوليو0
منشورات القناة
Kotlin inline и reified — зачем это вообще нужно Про inline-функции в Kotlin многие знают только то, что «они быстрее» и стоя
Kotlin inline и reified — зачем это вообще нужно Про inline-функции в Kotlin многие знают только то, что «они быстрее» и стоят в стандартной библиотеке рядом с map и filter. На самом деле inline — это не про производительность в чистом виде, а про то, что можно сделать невозможное в обычных функциях: reified generics, non-local return из лямбд и полноценные DSL без runtime-оверхеда. Разберём по порядку. Базовое поведение: inline вставляет тело функции прямо в место вызова:
inline fun measureTime(block: () -> Unit): Long {
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}

measureTime { doWork() }
// компилятор превращает в:
// val start = System.currentTimeMillis()
// doWork()
// val time = System.currentTimeMillis() - start
Никакого объекта-лямбды, никакого вызова invoke — код разворачивается на месте. Для маленьких хелперов с лямбдами это спасает от аллокаций в горячих циклах. Главная фича — reified type parameters. В обычной функции T стирается в рантайме, и T::class или is T не работают. Внутри inline-функции с reified тип сохраняется, потому что тело подставляется по месту:
inline fun <reified T> Any?.safeCast(): T? {
    return if (this is T) this else null
}

val x: Any = "hello"
val s: String? = x.safeCast() // работает
На этом стоит весь Gson-style API: gson.fromJson<UserDto>(json) вместо gson.fromJson(json, UserDto::class.java). Или в Compose: hiltViewModel<HomeViewModel>() вместо явной передачи класса. Второй кейс — non-local return из лямбды. Обычная лямбда не может сделать return из внешней функции, только return@label. Внутри inline-лямбды return работает как в обычном коде:
fun findUser(id: Long): User? {
    users.forEach { user ->
        if (user.id == id) return user  // выход из findUser
    }
    return null
}
Работает потому, что forEach в stdlib — inline. Без этого пришлось бы возвращать из forEach через return@forEach и потом отдельно проверять флаг. crossinline и noinline — модификаторы для параметров-лямбд. noinline говорит компилятору «эту лямбду не разворачивай, оставь как объект» — нужно, если её передают дальше в обычную функцию. crossinline запрещает non-local return, что критично, когда лямбда вызывается не сразу, а в колбэке или корутине. Подводные камни тоже есть. Inline-функции разворачиваются в каждом месте вызова, поэтому для больших функций (десятки строк) это раздувает байткод — компилятор ругается предупреждением. Инлайнить имеет смысл функции с параметрами-лямбдами, чтобы убрать аллокации, или там, где reified/non-local return реально нужны. Для обычной логики без лямбд inline ничего не даёт, а иногда даже замедляет за счёт большего DEX. Если видите fun <T> ... с передачей T::class.java, а рядом есть inline reified вариант — второй почти всегда чище. А вы где используете inline и reified в своих API? #mobilevkhub #kotlin

2
Анимации в Jetpack Compose — шесть инструментов на разные случаи В Compose анимаций много, и легко запутаться, что где примен+5
Анимации в Jetpack Compose — шесть инструментов на разные случаи В Compose анимаций много, и легко запутаться, что где применять. В карточках разберёмся с шестью основными, которые закрывают почти всё: 🟣 animate*AsState для одного значения 🟣 updateTransition для связанных 🟣 AnimatedVisibility для появления и исчезновения 🟣 AnimatedContent для смены контента 🟣 Crossfade для плавной замены 🟣 InfiniteTransition для повторяющихся эффектов #mobilevkhub #compose #анимация
242
3
Как ускорить старт приложения на 25–40% без переписывания кода Baseline Profiles — это метаданные, которые сообщают ART, каки
Как ускорить старт приложения на 25–40% без переписывания кода Baseline Profiles — это метаданные, которые сообщают ART, какие классы и методы нужно AOT-скомпилировать заранее, ещё до первого запуска. Обычно ART компилирует «горячий» код JIT-ом уже во время работы приложения, и на холодном старте это добавляет секунды. Baseline Profiles сдвигают компиляцию с устройства пользователя на этап сборки. Как работает При сборке в APK попадает baseline-prof.txt со списком классов и методов для критичных user journey. При установке ProfileInstaller передаёт список в ART, и тот компилирует AOT сразу, не откладывая до JIT в рантайме. Результат — плавнее старт и скролл, меньше jank на первом запуске новой версии. Настройка Нужен модуль под MacroBenchmark, который прогонит критичный user journey и соберёт профиль: @RunWith(AndroidJUnit4::class) class BaselineProfileGenerator { @get:Rule val rule = BaselineProfileRule() @Test fun generate() = rule.collect( packageName = "com.example.app" ) { pressHome() startActivityAndWait() // прогоняем главный экран, скролл, открытие типового кейса device.wait(Until.hasObject(By.text("Feed")), 5_000) } } После прогона в src/release/generated/baselineProfiles/baseline-prof.txt появится список правил. Baseline Profile Gradle Plugin автоматически подкладывает его в APK при релизной сборке. Startup Profiles — надстройка над Baseline Profiles. Тот же файл, но помечает подмножество классов, критичных именно для запуска. R8 использует его для DEX layout optimization — размещает эти классы в первом classes.dex, чтобы ART не пришлось загружать несколько DEX-файлов при старте. Ловушки Профиль собирают на варианте с isMinifyEnabled = false, применяют к билду с включённым R8 — плагин делает это автоматически. На эмуляторе прироста не будет, замер только на физическом устройстве, лучше entry-level. Переснимать профиль нужно при заметных изменениях архитектуры: старый на новых экранах ничего не даст. Cloud Profiles — параллельный механизм Google Play. Google собирает агрегированные профили по реальным пользователям и раздаёт с обновлением, но новая версия долетает с задержкой в день-два. Baseline Profiles работают с момента установки, поэтому одно другое не заменяет — оба вместе дают лучший результат. Если в проекте до сих пор нет Baseline Profiles и Startup Profiles — это самое простое, что можно сделать для перформанса без единой строки продуктового кода. Плюс включение R8 c isMinifyEnabled = true — все три штуки вместе и дают 25–40% прироста к производительности. #mobilevkhub #kotlin #baseline
262
4
Как VK пере езжали с XML на Jetpack Compose: базовые классы и паттерны В большом Android-проекте VK с сотнями экранов долго ж+5
Как VK пере езжали с XML на Jetpack Compose: базовые классы и паттерны В большом Android-проекте VK с сотнями экранов долго жила классика — XML-вёрстка, Dagger 2, Cicerone для навигации, MVVM. С ростом кодовой базы связка начала подводить: разрозненные MutableStateFlow для каждого поля состояния давали моргания интерфейса (isLoading уже false, а items ещё пустой), фрагменты обрастали обвязкой на 200 строк, ревью экранов превращалось в путешествие по вложенным <include> и <merge>. Полтора года команда переезжала на Compose с параллельной сменой MVVM на MVI, не останавливая продуктовую разработку и сохраняя crash-free на 99,95%+. В карточках рассказываем, как: 🔵отказались от разрозненных Flow ради единого State в MVI 🔵собрали базовый MviViewModel с атомарным updateUiState 🔵превратили жирный BaseFragment в тонкий MviFragment на 20 строк 🔵обошлись без передачи ViewModel в Composable через паттерн ScreenCallbacks 🔵сделали сложные списки в LazyColumn через интерфейс ComposeLazyItem ➡️ Полная версия статьи на Хабре — про Gradle-конфигурацию с convention plugin, ComposableLifecycleObserver, паттерн provide для делегирования отрисовки элементов списка и планы на второй и третий этапы миграции: переход с Dagger 2 на Koin и с Cicerone на Compose Navigation.
322
5
Compose Stability — почему компоненты рекомпозятся и как это починить В Compose есть механика skippable composables. Если пар
Compose Stability — почему компоненты рекомпозятся и как это починить В Compose есть механика skippable composables. Если параметры функции стабильны и значения не изменились, Compose пропускает её при рекомпозиции и переиспользует прошлый результат. На этом держится производительность фреймворка. Беда в том, что большинство параметров по умолчанию нестабильны, и LazyColumn неожиданно рекомпозится целиком при изменении одного элемента. Классический пример — обычный List: @Composable fun ProductList(items: List<Product>) { LazyColumn { items(items) { ProductCard(it) } } } List<T> — интерфейс, который может скрывать MutableList под капотом, поэтому компилятор Compose помечает его как unstable. Любая рекомпозиция родителя — и весь список перерендеривается, даже если данные те же. Три способа починить Первый — @Immutable на data-классе содержимого: @Immutable data class Product(val id: Long, val name: String, val price: Int) Compose доверяет и помечает класс как stable. Второй — ImmutableList из kotlinx-collections-immutable: fun ProductList(items: ImmutableList<Product>) { ... } ImmutableList гарантированно неизменяемый — у компилятора нет сомнений. Третий — Stability Configuration File. С Compose Compiler 1.5.5 можно помечать stable-классы из чужих модулей (LocalDateTime, BigDecimal, legacy). Пишем в compose_compiler_config.conf пакеты или классы, подключаем через stabilityConfigurationFile. Главное изменение Compose 1.7 (август 2024) — Strong Skipping Mode стал default. Теперь все restartable composables становятся skippable, даже с unstable параметрами. Unstable параметры сравниваются по === (instance equality), stable — по equals(). Это спасает от большинства проблем «параметр нестабилен → composable рекомпозится». Но не от всех. Если на каждой рекомпозиции создаётся новый объект — instance equality не помогает: // плохо -- новый List на каждой рекомпозиции val items = state.value.map { it.toUi() } ProductList(items) // хорошо -- мемоизация val items = remember(state.value) { state.value.map { it.toUi() }.toImmutableList() } Бонус Strong Skipping — лямбды с unstable captures теперь запоминаются автоматически. Раньше любая лямбда, захватывающая var из родителя, делала composable нестабильным. Как найти проблемы в проде Включаем compose-metrics через freeCompilerArgs в Gradle — после сборки в build/compose_compiler/ лежат отчёты *-classes.txt и *-composables.txt. Там видно, какие классы unstable и какие composable не skippable. Точечно лечим самое горячее — обычно это пара классов в shared/domain слое, после фикса которых рекомпозиции падают в разы. Производительность Compose — это в первую очередь про стабильность параметров. Если LazyColumn тормозит без очевидной причины, первый шаг — отчёт. #mobilevkhub #compose
293
6
Modifier в Jetpack Compose — где порядок имеет значение Modifier в Compose — то место, где порядок применения реально меняет+5
Modifier в Jetpack Compose — где порядок имеет значение Modifier в Compose — то место, где порядок применения реально меняет результат. .padding().background() и .background().padding() дают разные картинки на экране, и большинство багов на старте — это перепутанный порядок модификаторов. В карточках — шесть конкретных мест: порядок padding и background, размер клик-зоны, как работает size, объединение трансформаций через graphicsLayer, иммутабельность Modifier и тонкости composed для собственных модификаторов. #mobilevkhub #compose
362
7
🤖 Android 🟣 Android 17: Compose-first официально С выходом Android 17 Google окончательно закрепила курс на Compose: новые
🤖 Android 🟣 Android 17: Compose-first официально С выходом Android 17 Google окончательно закрепила курс на Compose: новые API, Jetpack-библиотеки и инструменты будут развиваться прежде всего вокруг декларативного UI. 🟣Android XR SDK Preview 4 XR-эмулятор теперь встроен в Android Studio, а поддержка Compose, Unreal и Godot делает Android XR заметно ближе к реальным продуктовым сценариям. 🟣R8 Configuration Analyzer В AGP 9.3 появился инструмент, который показывает, какие keep rules мешают оптимизации и обфускации. Для крупных приложений это может дать быстрый прирост в размере и производительности сборок. 🟣ADK для Android Google представила open-source фреймворк для ИИ-агентов с поддержкой on-device моделей через ML Kit GenAI и AICore. Похоже на начало полноценной ИИ-инфраструктуры внутри Android-стека. ▶️ iOS 🟣WWDC26: Foundation Models и Agentic Apps Главный тренд года — встроенный ИИ. Apple активно развивает Foundation Models, агентные сценарии и инструменты для интеграции ИИ прямо в приложения. 🟣Системные промпты и SwiftUI-скиллы для Xcode 27 В первой бете появились новые ИИ-возможности и набор встроенных промптов и скиллов. Xcode всё глубже интегрирует ИИ в ежедневную разработку. 🟣UIKit адаптируется под новые формфакторы Apple продолжает готовить платформу к многооконности, большим экранам и внешним дисплеям. Новые API управления навигацией, сценами и таббаром показывают, куда движется экосистема. 🟣Goodnotes о переходе в браузер через SwiftWasm Один из крупнейших Swift-продуктов показал реальный кейс переиспользования кодовой базы в вебе без переписывания на JavaScript. Важный сигнал для будущего Swift за пределами экосистемы Apple. ❤️ Новые статьи от инженеров VK на Хабре Применение Kotlin DSL в TeamCity для автоматизации пайплайнов: кейс команды ВКонтакте #дайджест #mobilevkhub
339
8
Что и когда выбирать в Kotlin После StateFlow обычно встаёт следующий вопрос: а как делать события, которые должны прийти каж
Что и когда выбирать в Kotlin После StateFlow обычно встаёт следующий вопрос: а как делать события, которые должны прийти каждому подписчику? Или одну задачу на нескольких воркеров? Здесь и начинается выбор между SharedFlow и Channel — двумя инструментами, которые часто путают. Разберём, чем они отличаются и где применять каждый. SharedFlow — горячий поток событий, который получают все активные подписчики: private val _events = MutableSharedFlow<Event>( replay = 0, extraBufferCapacity = 16, onBufferOverflow = BufferOverflow.DROP_OLDEST ) val events: SharedFlow<Event> = _events.asSharedFlow() // в одном месте _events.emit(Event.NavigateTo("settings")) // в любом collect events.collect { event -> handle(event) } replay — сколько последних событий повторно отдать новому подписчику, по умолчанию 0 (старые не доходят). extraBufferCapacity — буфер на случай медленных подписчиков. onBufferOverflow — стратегия при переполнении: SUSPEND, DROP_OLDEST, DROP_LATEST. Главное свойство — все активные подписчики получают все события. Идеально для broadcast: navigation, snackbar messages, toast-уведомления, аналитические события. Channel — очередь с send/receive, где каждое сообщение получает ровно один потребитель: val tasks = Channel<Task>( capacity = Channel.BUFFERED, onBufferOverflow = BufferOverflow.SUSPEND ) // producer launch { while (true) tasks.send(fetchTask()) } // несколько worker-ов, каждое сообщение получает один из них repeat(4) { launch { for (task in tasks) process(task) } } Если четыре воркера читают один канал, каждый task достанется только одному из них. Это work distribution — то, что в SharedFlow сделать нельзя в принципе. Channel умеет закрываться (close()) и сигнализировать завершение через for (x in channel). SharedFlow — нет, он живёт, пока живёт scope. Когда что выбирать SharedFlow — для broadcast (один producer → много consumer): UI events, navigation, snackbar, обновления, которые должен увидеть каждый подписчик. Channel — для work distribution (один producer → один из многих consumer): producer-consumer между корутинами, когда сообщение нельзя потерять, очереди задач для пула воркеров. Частая ошибка — использовать SharedFlow там, где нужно гарантировать обработку одним потребителем. С replay = 0 и медленным подписчиком события просто теряются: emit прошёл, никто не успел, всё. Для гарантированной доставки — Channel с capacity = UNLIMITED или SUSPEND. Обратная ошибка — Channel там, где нужен broadcast. Каждое сообщение уйдёт только одному коллектору, остальные не увидят. Получаем сломанную навигацию из второго экрана. #mobilevkhub #kotlin
385
9
Side effects в Jetpack Compose — шесть инструментов для шести задач В Compose нет привычных lifecycle-методов, как у Activity+5
Side effects в Jetpack Compose — шесть инструментов для шести задач В Compose нет привычных lifecycle-методов, как у Activity или Fragment. Composable вызывается часто и в непредсказуемом порядке. Поэтому любой код, который должен выполниться один раз, при смене параметра или с подпиской и отпиской, оборачиваем в специальный эффект. Разбираем шесть инструментов под шесть задач в карточках. #mobilevkhub #compose
368
10
Flow в Kotlin закрывает то, ради чего раньше тащили RxJava — асинхронные потоки значений. С 2020 это стандарт в Android и KMP
Flow в Kotlin закрывает то, ради чего раньше тащили RxJava — асинхронные потоки значений. С 2020 это стандарт в Android и KMP, базу учим за вечер. А вот в StateFlow, collectLatest и WhileSubscribed закопано несколько грабель, на которые наступают даже опытные. Поток создаётся через flow {}: fun timer(): Flow<Int> = flow { var count = 0 while (true) { emit(count++) delay(1000) } } Корутина внутри стартует, только когда появился первый подписчик, — это и есть cold flow. Каждый новый collect получает поток с нуля, подписался дважды — запросы пошли дважды. Частый баг: один flow собирают и во ViewModel, и в Compose, а потом удивляются, почему запросов к API в два раза больше ожидаемого. collect блокирует корутину до конца потока: следующее emit ждёт, пока обработается текущее. Бесплатный backpressure из коробки. Когда нужна обратная стратегия — взять только самое свежее и забить на всё начатое — есть collectLatest: searchQuery.collectLatest { query -> val results = repository.search(query) showResults(results) } collectLatest отменяет блок только в suspend-точках. Чистый CPU-bound код без delay, без сети, без yield добежит до конца, и только потом запустится новый. Для тяжёлых вычислений ставим yield() или уносим на Dispatchers.Default. StateFlow — горячий поток, который всегда держит одно актуальное значение. Новый подписчик мгновенно получает последнее, не дожидаясь следующего emit: class FeedViewModel : ViewModel() { private val _state = MutableStateFlow<UiState>(UiState.Loading) val state: StateFlow<UiState> = _state.asStateFlow() fun load() = viewModelScope.launch { _state.value = UiState.Content(repository.fetch()) } } Это корутины без привязки к Android, с поддержкой KMP. В Compose это основной канал из ViewModel в UI. Когда есть холодный источник (Flow от Room или Retrofit) и его нужно расшарить между подписчиками — есть stateIn: val users: StateFlow<List<User>> = repository .observeUsers() .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) Тут две вещи, которые часто упускают. Первая: 5_000 — это stopTimeoutMillis, подписка на источник держится ещё 5 секунд после ухода последнего подписчика, ровно чтобы пережить поворот экрана без перезапуска запросов. Второй параметр WhileSubscribed — replayExpirationMillis, через сколько забыть последнее значение, по дефолту Long.MAX_VALUE. Вторая: scope обязательно viewModelScope, не lifecycleScope. Lifecycle scope умирает при ротации — и stateIn вместе с ним. После пересоздания подписка стартует заново, UI на секунду моргает initialValue. ViewModel переживает ротацию и держит StateFlow живым. На практике flow {}, StateFlow, collectLatest и stateIn закрывают 90% задач. Дальше debounce, throttle, мультиплексирование — уже под конкретный кейс. #mobilevkhub #kotlin
415
11
XCTest жил в Swift с самого начала и был тонкой надстройкой над тестовой моделью Objective-C: классы-наследники XCTestCase, м+5
XCTest жил в Swift с самого начала и был тонкой надстройкой над тестовой моделью Objective-C: классы-наследники XCTestCase, методы с префиксом test, около 40 ассертов, отдельный test plan для управления параллелизмом. На WWDC 2024 Apple представила Swift Testing — нативный фреймворк на макросах, который поставляется с Xcode 16, открыт под Apache 2.0, работает на Linux и Windows. Server-side проекты вроде swift-aws-lambda-runtime убрали зависимость от XCTest ещё в июне 2025 года. Чинить старый фреймворк выходило дороже, чем сделать новый. Развивать XCTest означало тащить за собой Obj-C runtime: классовое наследование XCTestCase, discovery тестов по имени функции, stringly-typed-ассерты, которые при провале знают только runtime-значения, но не само выражение. Параллелизм работал через запуск нескольких процессов, по тесту на процесс, async/await выражался через XCTestExpectation. С приходом Swift Macros в Swift 5.9 у Apple появился чистый инструмент: discovery, ассерты и конфигурация — всё на этапе компиляции, без runtime reflection. Рассказываем об основных фичах Swift Testing в карточках. #mobilevk #обзор
480
12
Compose сам решает, какие composable можно пропустить при рекомпозиции: если функция skippable и все её параметры не изменили
Compose сам решает, какие composable можно пропустить при рекомпозиции: если функция skippable и все её параметры не изменились, повторного вызова не происходит. Правило ломалось на параметрах, которые компилятор не мог считать stable, — LocalDateTime, List<T> вместо ImmutableList<T>, любой класс из чужого модуля без @Stable. Один такой параметр блокировал skip и каскадно дёргал рекомпозицию вглубь. Команды реагировали аннотациями @Stable и @Immutable на каждом доменном классе, обёртками над списками и remember на каждой лямбде. Получался шум, который сложно сопровождать и легко поставить неправильно — @Stable на классе с фактически мутабельным состоянием создаёт скрытые баги. Strong skipping (Compose Compiler 1.5.4+, по умолчанию в Compose 1.7) изменил два правила. Первое: unstable-параметры сравниваются по instance equality (===) вместо немедленного отказа от skip. Если ViewModel вернул тот же экземпляр Order, composable пропускается, даже если класс формально нестабилен. @Composable fun OrderRow(order: Order, onTap: () -> Unit) { ... } // раньше: unstable Order → всегда рекомпозиция // теперь: skip, если order === предыдущему order Второе важнее: лямбды с unstable-захватами компилятор оборачивает в remember автоматически. // раньше приходилось писать руками val onTap = remember(order.id) { { viewModel.markPaid(order.id) } } // со strong skipping эквивалент пишется компилятором: val onTap = { viewModel.markPaid(order.id) } // → разворачивается в remember(viewModel, order.id) { { ... } } Из практического: большую часть @Stable и @Immutable на доменных классах можно убирать. Они не вредят, но добавляют шум в коде и в дифах. Три места, где нужно остановиться. // 1. LazyListScope-лямбды НЕ мемоизируются автоматически -- // они живут вне @Composable. Если контент тяжёлый, оборачивайте руками. LazyColumn { items(orders, key = { it.id }) { order -> OrderRow(order, onTap = remember(order.id) { { ... } }) } } // 2. @Stable нужен, если ViewModel выдаёт НОВЫЕ инстансы // с теми же данными -- типичный случай DTO → UI-model маппинга @Stable data class OrderUi(val id: String, val total: Money) // 3. enum и sealed class инферятся stable без аннотации -- она не нужна Включение, если ещё не на 1.7+ // build.gradle.kts composeCompiler { enableStrongSkippingMode.set(true) } Эффект меряйте через Layout Inspector с включёнными recomposition counts или composition tracing. Без замеров легко поверить, что стало быстрее, тогда как часть scope всё ещё перерисовывается на каждой эмиссии состояния. И отдельно — key в LazyColumn обязателен, без него счётчики рекомпозиции показывают завышенные значения независимо от skipping-режима. #mobilevkhub #strongskipping
217
13
🤖 Android 🟣 Metro DI вышел в стабильной версии. Это новый DI-фреймворк для Kotlin и KMP — без KAPT и KSP, генерация через K
🤖 Android 🟣 Metro DI вышел в стабильной версии. Это новый DI-фреймворк для Kotlin и KMP — без KAPT и KSP, генерация через Kotlin Compiler Plugin, граф зависимостей проверяется на этапе компиляции. 🟣 Context Parameters получили статус Stable в Kotlin 2.4.0-Beta2. Зависимости передаются через контекст вместо протаскивания через сигнатуры функций. 🟣 JetBrains обновили дефолтную структуру KMP-проектов: общий код теперь живёт в shared, под каждую платформу — отдельный application-модуль. Изменение связано с AGP 9. 🟣 Jetpack Paging 3.5.0 добавил работу с данными как со StateFlow и явные методы append(), prepend(), refresh(), retry(). Пагинация стала управляемее в Compose-сценариях. 🟣 AndroidX WebKit 1.16.0 — стабильный async-старт WebView. WebView можно прогревать заранее, а Navigation API даёт доступ к этапам навигации и метрикам FCP/LCP без JS. 🟣 Jetpack Telecom 1.1.0: VoIP-звонки отображаются в системной истории вызовов, callback работает прямо из нативного дайлера. Фича доступна на Android 16.1+. 🟣 Android Bench — бенчмарк LLM для Android-разработки. В свежем исследовании GPT 5.5 и 5.4 показали себя сильнее Claude. ▶️ iOS 🟣 Опубликовали записи докладов с try! Swift Tokyo 2026. Из интересного — выступления про Swift Concurrency Type System, скрытую силу Async Sequences и то, почему SwiftUI устроен именно так. 🟣 Apple показала финалистов Apple Design Awards 2026. Это хороший ориентир не только по визуальному качеству, но и по тому, какие паттерны Apple сейчас считает сильными: нативность, аккуратная работа с платформой, доступность и внимание к деталям. 🟣 Swift Concurrency: два материала про подводные камни. Первый — про неочевидные suspension points, из-за которых операции ведут себя непредсказуемо. Второй — про concurrency crashes в Swift 6: часть проблем ловится в runtime на границах акторов, GCD, Core Data и delegate callbacks. 🟣 Task.immediate в Swift 6.2: async-работа начинается сразу в текущем execution context до первого настоящего suspension point. Нюанс небольшой по формулировке, но важный для производительности. 🟣 Сравнение способов защиты shared state: actors, DispatchQueue и locks — с разбором, когда что применять. 🟣 Floating Safe Area Bar в SwiftUI: практический пример всплывающей карточки с CTA-кнопкой через safeAreaBar, вариант для iOS 26 и fallback для iOS 18. 🟣 Гайд по FormatStyle — шпаргалка для форматирования дат, чисел и других значений без кастомных решений. #дайджест #mobilevkhub
506
14
📱 Официальный релиз финальной стабильной версии Android 17 запланирован на июнь 2026 года, первым его получат Pixel, затем S
📱 Официальный релиз финальной стабильной версии Android 17 запланирован на июнь 2026 года, первым его получат Pixel, затем Samsung, а начиная с осени все остальные устройства. Кодовое имя — Cinnamon Bun. Для приложений с targetSdkVersion = 37 Google убирает лазейки, которые позволяли игнорировать адаптивное поведение на больших экранах. И это не единственное ломающее изменение. Ориентация и изменение размера Начиная с Android 16 Google двигался в сторону адаптивной разработки. Android 17 делает это обязательным, приложения с таргетом SDK 37 больше не могут блокировать поворот или ресайз на планшетах и складных устройствах. Ограничения на ориентацию и размер окна игнорируются на больших экранах. Если приложение до сих пор живёт в портрете и использует setRequestedOrientation() как костыль, то на Android 17 оно перестанет работать. Среда выполнения и рефлексия ART получает реализацию android.os.MessageQueue без блокировок, чтобы снизить конкуренцию потоков и уменьшить число пропущенных кадров. Это сломает код, который через рефлексию лезет в приватные поля MessageQueue. Второе изменение жёстче. Мутация static final полей через рефлексию теперь бросает IllegalAccessException, а запись через JNI может уронить приложение. Библиотеки, которые полагались на патчинг во время выполнения, перестанут работать. Сертификаты и сеть Прозрачность сертификатов теперь включена по умолчанию для Android 17. Открытый HTTP-трафик заблокирован, а флаг android:usesCleartextTraffic="true" без явной конфигурации сетевой безопасности не поможет. Если приложение ещё ходит по HTTP, то пора переходить на HTTPS с корректной конфигурацией. Локальная сеть под разрешением Новое runtime-разрешение ACCESS_LOCAL_NETWORK: по умолчанию приложение не имеет доступа к локальной сети. Это закрывает возможность фингерпринтинга, но ломает код, который раньше ходил в локальную сеть без разрешений. Безопасная загрузка нативных библиотек System.load() теперь требует, чтобы загружаемая нативная библиотека была доступна только для чтения. Иначе упадёт UnsatisfiedLinkError. Это закрывает класс атак через загрузку вредоносного кода, но ломает библиотеки, которые писали в файл перед загрузкой. Фоновый звук и SMS Фоновое воспроизведение, запросы аудиофокуса и API изменения громкости ограничены для фоновых контекстов. Обработка SMS тоже стала консервативнее: стандартные одноразовые коды задерживаются для большинства таргетов, чтобы снизить риск перехвата. Что делать сейчас Четыре шага до стабильного релиза: 🟣проверьте рефлексию и JNI-вызовы вокруг MessageQueue и static final 🟣проведите аудит конфигурации сетевой безопасности 🟣прогоните QA на больших экранах с targetSdkVersion = 37 🟣протестируйте фоновый звук и SMS Target SDK 37 — это не формальность. На этот раз Google режет не только API, но и привычные обходные пути. #android17 #mobilevkhub
531
15
✨ Каждый Intent, каждый запрос к системному сервису, каждый callback из Service в Activity проходит через Binder. Подсистема+5
✨ Каждый Intent, каждый запрос к системному сервису, каждый callback из Service в Activity проходит через Binder. Подсистема старше публичной версии Android, и большинство разработчиков работает с её обёртками, не заглядывая внутрь. #mobilevkhub
387
16
Compose сам решает, какие composable можно пропустить при рекомпозиции: если функция skippable и все её параметры не изменили
Compose сам решает, какие composable можно пропустить при рекомпозиции: если функция skippable и все её параметры не изменились, повторного вызова не происходит. Раньше правило ломалось на параметрах, которые компилятор не мог считать stable, — LocalDateTime, List<T> вместо ImmutableList<T>, любой класс из чужого модуля без @Stable. Один такой параметр блокировал skip и каскадно дёргал рекомпозицию вглубь. Команды реагировали аннотациями @Stable и @Immutable на каждом доменном классе, обёртками над списками и remember на каждой лямбде. Получался шум, который сложно сопровождать и легко поставить неправильно — @Stable на классе с фактически мутабельным состоянием создаёт скрытые баги. Strong skipping (Compose Compiler 1.5.4+, по умолчанию в Compose 1.7) изменил два правила. Первое: unstable-параметры сравниваются по instance equality (===) вместо немедленного отказа от skip. Если ViewModel вернул тот же экземпляр Order, composable пропускается, даже если класс формально нестабилен. fun OrderRow(order: Order, onTap: () -> Unit) { ... } // раньше: unstable Order → всегда рекомпозиция // теперь: skip, если order === предыдущему order Второе важнее: лямбды с unstable-захватами компилятор оборачивает в remember автоматически. val onTap = remember(order.id) { { viewModel.markPaid(order.id) } } // со strong skipping эквивалент пишется компилятором: val onTap = { viewModel.markPaid(order.id) } // → разворачивается в remember(viewModel, order.id) { { ... } } Из практического: большую часть @Stable и @Immutable на доменных классах можно убирать. Они не вредят, но добавляют шум в коде и в дифах. Три места, где нужно остановиться: // они живут вне @Composable. Если контент тяжёлый, оборачивайте руками. LazyColumn { items(orders, key = { it.id }) { order -> OrderRow(order, onTap = remember(order.id) { { ... } }) } } // 2. @Stable нужен, если ViewModel выдаёт НОВЫЕ инстансы // с теми же данными -- типичный случай DTO → UI-model маппинга @Stable data class OrderUi(val id: String, val total: Money) // 3. enum и sealed class инферятся stable без аннотации -- она не нужна Включение, если ещё не на 1.7+ : composeCompiler { enableStrongSkippingMode.set(true) } Эффект меряйте через Layout Inspector с включёнными recomposition counts или composition tracing. Без замеров легко поверить, что стало быстрее, тогда как часть scope всё ещё перерисовывается на каждой эмиссии состояния. И отдельно — key в LazyColumn обязателен, без него счётчики рекомпозиции показывают завышенные значения независимо от skipping-режима. #mobilevkhub #compose #strongskipping
479
17
Array, String, Dictionary — структуры, то есть value types: по правилам копируются при каждом присвоении. На практике копиров+5
Array, String, Dictionary — структуры, то есть value types: по правилам копируются при каждом присвоении. На практике копирование происходит редко. Между этими двумя фактами лежит copy-on-write. #mobilevkhub
410
18
derivedStateOf — когда скролл перерисовывает экран каждый пиксель Compose перерисовывает composable, когда читаемое им состоя
derivedStateOf — когда скролл перерисовывает экран каждый пиксель Compose перерисовывает composable, когда читаемое им состояние меняется. Со скроллом это создаёт проблему: lazyListState.firstVisibleItemIndex меняется при каждом пикселе прокрутки. Если кнопка «Наверх» читает это значение напрямую — она перерисовывается сотни раз в секунду, хотя визуально ничего не меняется. // Перерисовывается при каждом пикселе скролла @Composable fun ScrollScreen() { val listState = rememberLazyListState() val showButton = listState.firstVisibleItemIndex > 0 LazyColumn(state = listState) { ... } if (showButton) ScrollToTopButton() } showButton пересчитывается на каждый frame. Если список рендерит сложные элементы — это заметно на слабых устройствах. derivedStateOf говорит Compose: «пересчитывай значение при изменении источника, но рекомпозицию запускай только если результат изменился». @Composable fun ScrollScreen() { val listState = rememberLazyListState() val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } LazyColumn(state = listState) { ... } if (showButton) ScrollToTopButton() } Теперь showButton пересчитывается на каждый пиксель — это внутренняя работа Compose. Но рекомпозиция ScrollScreen происходит только в двух случаях: когда firstVisibleItemIndex переходит с 0 на 1 и обратно. remember { derivedStateOf { ... } } — стандартная связка. Без remember derivedStateOf пересоздаётся при каждой рекомпозиции. Без derivedStateOf — рекомпозиция на каждое изменение источника. Нужны оба. Паттерн полезен везде, где состояние меняется часто, а UI реагирует редко: // Показать заголовок только когда проскроллили дальше порога val showHeader by remember { derivedStateOf { listState.firstVisibleItemScrollOffset > 100 } } // Кнопка отправки активна только когда оба поля заполнены val isFormValid by remember { derivedStateOf { name.isNotBlank() && email.contains("@") } } Важно понять, что derivedStateOf создан не для бизнес-логики и не для тяжёлых вычислений. Это инструмент для трансформации часто меняющегося UI-состояния в редко меняющееся прямо в composable. Если логика требует данных из репозитория или занимает время — это задача для ViewModel с map на Flow. #mobilevk #android #kotlin #jetpackcompose
469
19
@Observable в SwiftUI До iOS 17 реактивный state в SwiftUI строился на ObservableObject с @Published. Это работало, но у подх+5
@Observable в SwiftUI До iOS 17 реактивный state в SwiftUI строился на ObservableObject с @Published. Это работало, но у подхода был системный изъян: любое изменение любого @Published-свойства перерисовывало все вью, подписанные на объект целиком — даже те, которым это свойство не нужно. В iOS 17 появился макрос @Observable, который решает это на уровне компилятора. #mobilevk #ios #observable
430
20
12–13 мая состоится Mobius Spring. В преддверии конференции мы поговорили с нашими постоянными стендистами — руководителями A+5
12–13 мая состоится Mobius Spring. В преддверии конференции мы поговорили с нашими постоянными стендистами — руководителями Android-разработки VK Александром Жеребцовым и Богданом Мащенко. Ребята рассказали, зачем вообще нужны конференции и что нас ждёт на стенде VK в этом году. Все подробности — в карточках 👆 #mobilevk #mobiusspring
447