Нативна розробка Android на Kotlin
RecyclerView з DiffUtil.calculateDiff() на main thread, список з 500 елементів, середньостатистичний Android-телефон 2021 року — і користувач отримує 200–400 мс фриз при кожному оновленні даних. Перенеси розрахунок diff у фоновий потік через AsyncListDiffer — проблема зникає. Такі речі не очевидні без профайлера та розуміння threading-моделі Android.
Kotlin + Jetpack Compose + Coroutines — поточний production-стандарт для нативної Android-розробки. XML та View system не зникли, але нові проекти починаються з Compose.
Jetpack Compose: як працює рекомпозиція та чому це важливо
Compose — декларативний UI-фреймворк. Замість TextView.setText() та adapter.notifyItemChanged() — composable функції, які описують UI як функцію стану. При зміні стану Compose перевичисляє тільки затронуті частини дерева. Це називається рекомпозиція.
Проблема: рекомпозиція може бути надто частою. Якщо передати в composable лямбду, створену при кожній рекомпозиції батька — дочірній composable буде рекомпонуватися щоразу, навіть якщо видимі дані не змінились.
// Погано — нова лямбда при кожній рекомпозиції, дочірня компонента
// думає, що параметр змінився
@Composable
fun ParentScreen(viewModel: MyViewModel = hiltViewModel()) {
val items by viewModel.items.collectAsState()
ItemList(
items = items,
onItemClick = { id -> viewModel.selectItem(id) } // створюється заново кожен раз
)
}
// Добре — remember стабілізує лямбду
@Composable
fun ParentScreen(viewModel: MyViewModel = hiltViewModel()) {
val items by viewModel.items.collectAsState()
val onItemClick = remember { { id: String -> viewModel.selectItem(id) } }
ItemList(items = items, onItemClick = onItemClick)
}
Stability та @Stable/@Immutable
Compose визначає, чи потрібна рекомпозиція composable, перевіряючи стабільність параметрів. Тип вважається стабільним, якщо Compose може гарантувати: якщо два значення рівні за equals(), їх UI-представлення однакове.
Примітиви, String, data-класи з val-полями стабільних типів — стабільні автоматично. List<T> — нестабільний, тому що це інтерфейс. MutableList може змінитися без сповіщення. Рішення — ImmutableList з kotlinx.collections.immutable або @Immutable-анотація на data-класі.
LazyColumn та продуктивність списків
LazyColumn — еквівалент RecyclerView у Compose. key у items { } — обов'язковий параметр для будь-якого списку, де елементи можуть переміщуватися або видалятися. Без key Compose не може розрізнити переміщення елемента від видалення одного та додавання іншого, що ломає анімації та може вислати дивне скидання стану ячейки.
contentType — додаткова оптимізація. При наявності кількох типів ячейок Compose може переиспользовать composition для ячейок одного типу. Це аналог getItemViewType у RecyclerView.
Kotlin Coroutines та архітектура
Coroutines — це не просто «зручний спосіб писати async-код». Це структурована конкурентність з чітким scope та lifecycle.
viewModelScope — coroutine scope, прив'язаний до lifecycle ViewModel. Коли ViewModel очищується (onCleared()), всі coroutines в scope автоматично скасовуються. Це усуває цілий клас утечок, типових для callback-based підходу.
Flow vs LiveData
StateFlow та SharedFlow — рекомендована заміна LiveData на Kotlin-проектах. LiveData lifecycle-aware, але прив'язаний до Android-платформи. Flow — чистий Kotlin, тестується без Android-залежностей.
collectAsState() у Compose підписується на StateFlow та тригерить рекомпозицію при новому значенні. lifecycleScope.launch { flow.collect { } } — для збірки в Fragment або Activity з врахуванням lifecycle через repeatOnLifecycle(Lifecycle.State.STARTED).
repeatOnLifecycle — це важливо. Без нього поток буде збиратися навіть коли програма у фоні, що може приводити до обробки UI-подій, коли вікно неактивне.
Dispatcher та структурована конкурентність
Dispatchers.IO — для сетевих запитів та файлових операцій. Dispatchers.Default — для CPU-intensive задач (парсинг, сортування, шифрування). Dispatchers.Main — для UI.
withContext(Dispatchers.IO) переключає coroutine на потрібний dispatcher без створення нового scope. Це ефективніше, ніж launch(Dispatchers.IO) всередину іншого launch.
Hilt та dependency injection
Hilt — офіційний DI-фреймворк для Android поверх Dagger 2. Усуває boilerplate Dagger: не потрібно писати Component та вручну зв'язувати Module з Component.
@HiltViewModel + @Inject constructor — ViewModel з інжекцією залежностей без фабрик. @Singleton, @ActivityScoped, @ViewModelScoped — правильний lifecycle для залежностей.
Типова помилка: використовувати @Singleton для репозиторію, який держить контекст Activity. Це утечка Activity. Правило: @Singleton тільки для залежностей, яким потрібен Application context або які не зберігають Android-специфічне стан.
WorkManager та фонові задачи
WorkManager — для гарантованих фонових задач, які повинні виконатися навіть після перезапуску програми або пристрою. Синхронізація даних, відправка аналітики, завантаження файлів.
CoroutineWorker — suspend-версія Worker. Працює в Dispatchers.IO за умовчанням.
Android 14 ужесточив вимоги до фонового виконання. FOREGROUND_SERVICE_TYPE став обов'язковим для foreground services. WorkManager правильно обробляє обмеження (мережа, зарядка) та не потребує foreground service для більшості задач.
Інструменти
Android Studio Profiler. CPU profiler з System Trace — видно все: coroutine suspension points, RenderThread, MainThread. Memory profiler — heap dump, allocation tracking. Network profiler — всі HTTP-запити з тілами.
Compose Layout Inspector. Дерево composable з указанням recomposition count. Видно, які composable рекомпонуються надто часто — точніше будь-якого логування.
LeakCanary. Автоматичне виявлення утечок пам'яті в development-сборці. Показує reference chain до утечки. Додається однією залежністю, працює без конфігурації.
Firebase Crashlytics + Performance Monitoring. Crash-free rate за версіями, Network request traces, Custom traces для критичних операцій.
Орієнтири
| Складність | Орієнтовний період |
|---|---|
| MVP (6–10 екранів, REST API) | 6–10 тижнів |
| Середня програма (20–30 екранів) | 3–5 місяців |
| Складна (платежі, ML Kit, Compose + custom UI) | 5–9 місяців |
Вартість розраховується після аналізу вимог та ТЗ.







