Витоки пам'яті в Android-застосунках — одна з найчастіших проблем у legacy-коді. ViewModel, що зберігає посилання на Activity, гарантує витік при кожному повороті екрану. Ми за 2–4 дні налаштовуємо чистий MVVM з Kotlin, Hilt та Jetpack, включаючи DI, тести та інтеграцію з мережею. Нижче — як ми це робимо та які помилки виправляємо.
Типові помилки, які ламають архітектуру
ViewModel з контекстом
Якщо ViewModel зберігає Context або посилання на Activity — це витік пам'яті та порушення сенсу патерну. ViewModel переживає перестворення Activity при повороті екрану. Для роботи з ресурсами використовуємо AndroidViewModel з Application-контекстом тільки коли іншого виходу немає, або виносимо рядки в окремий шар.
LiveData в Repository
Repository повертає LiveData<List<User>> — і тепер Repository прив'язаний до Android-фреймворку. Правильно: Repository працює з Flow<List<User>> (coroutines), а ViewModel конвертує в StateFlow через stateIn або .asLiveData().
Бізнес-логіка в ViewModel
ViewModel повинен трансформувати дані для UI, а не реалізовувати бізнес-правила. Складна логіка — в UseCase-класах між ViewModel та Repository.
Порівняння LiveData та StateFlow
| Характеристика | LiveData | StateFlow |
|---|---|---|
| Прив'язка до Android | Так (androidx.lifecycle) | Ні (чистий Kotlin) |
| Тестування | Вимагає InstantTaskExecutorRule | Через kotlinx-coroutines-test |
| Гарячий/холодний | Гарячий | Гарячий, але можна зробити холодним |
| Продуктивність | Базова | StateFlow в 3x швидший у ряді сценаріїв |
Як уникнути витоків пам'яті?
Не зберігайте посилання на Activity або Fragment у ViewModel. Використовуйте StateFlow або LiveData для передачі даних, а не контекст. Підписуйтесь через collectAsStateWithLifecycle() у Compose та скасовуйте корутини через viewModelScope. Офіційна документація ViewModel підтверджує, що ViewModel повинен переживати зміни конфігурації.
Як тестувати ViewModel з корутинами?
Використовуйте kotlinx-coroutines-test з TestDispatcher та Turbine для перевірки емісії StateFlow. Ми включаємо тести в кожен проєкт — це гарантує стабільність при рефакторингу. Наприклад, типовий тест перевіряє, що при успішному запиті uiState переходить в Success, а при помилці — в Error з повідомленням.
Як виглядає правильний MVVM на Kotlin
@HiltViewModel class UserProfileViewModel @Inject constructor( private val getUserProfile: GetUserProfileUseCase ) : ViewModel() { private val _uiState = MutableStateFlow<ProfileUiState>(ProfileUiState.Loading) val uiState: StateFlow<ProfileUiState> = _uiState.asStateFlow() fun loadProfile(userId: String) { viewModelScope.launch { getUserProfile(userId) .onSuccess { _uiState.value = ProfileUiState.Success(it) } .onFailure { _uiState.value = ProfileUiState.Error(it.message) } } } } sealed class ProfileUiState { object Loading : ProfileUiState() data class Success(val profile: UserProfile) : ProfileUiState() data class Error(val message: String?) : ProfileUiState() } У Fragment або Composable підписуємося на uiState через collectAsStateWithLifecycle() — це безпечніше, ніж collect, тому що автоматично зупиняє збір при переході в background.
Repository та джерела даних
Repository — єдина точка входу для ViewModel в дані. Реалізує протокол-інтерфейс. Всередині вирішує: брати з Room, з Retrofit або з кешу:
class UserRepositoryImpl @Inject constructor( private val api: UserApi, private val dao: UserDao ) : UserRepository { override fun getProfile(id: String): Flow<UserProfile> = flow { val cached = dao.getUser(id) if (cached != null) emit(cached.toDomain()) val remote = api.fetchUser(id) dao.insert(remote.toEntity()) emit(remote.toDomain()) } } Hilt для DI
Без Hilt в Android MVVM доводиться вручну створювати ViewModelFactory. Hilt (@HiltViewModel + @Inject) позбавляє цього: Dagger-граф генерується на етапі компіляції, помилки конфігурації видно одразу, а не в рантаймі.
Детальніше про налаштування Hilt
Додайте залежності в build.gradle (hilt-android та hilt-compiler), анотуйте клас Application @HiltAndroidApp. Для Activity та Fragment використовуйте @AndroidEntryPoint. Все інше підключається автоматично.Покрокове налаштування MVVM (5 кроків)
- Додайте залежності Hilt та Jetpack в build.gradle. Використовуйте останні стабільні версії.
- Створіть пакети data, domain, presentation.
- Визначте інтерфейс Repository та імплементацію з Room/Retrofit.
- Напишіть UseCase з бізнес-логікою.
- Реалізуйте ViewModel з sealed class для UI-станів.
Ми додаємо тести на ViewModel з використанням kotlinx-coroutines-test та Turbine. Це дозволяє виявляти регресії при кожній зміні.
Що входить в роботу
- Налаштування DI (Hilt) з модулями для Room, Retrofit, Repository.
- Створення UseCase для ключової бізнес-логіки.
- ViewModel з StateFlow та sealed class.
- Тести ViewModel з kotlinx-coroutines-test та Turbine.
- Міграція існуючого коду на MVVM (опціонально).
- Документація щодо структури пакетів.
Зв'яжіться з нами для обговорення вашого проєкту — ми безкоштовно оцінимо обсяг робіт. Отримайте консультацію, щоб обговорити деталі.
Наш досвід та гарантії
Ми займаємося Android-розробкою більше 5 років та реалізували 20+ проєктів. Гарантуємо, що архітектура буде без витоків, з підтримкою тестів та готова до масштабування. Замовте консультацію, щоб обговорити деталі.
Терміни орієнтовно
- Налаштування з нуля: 2–4 дні.
- Рефакторинг існуючого Activity-based проєкту: 1–3 тижні.
- Вартість розраховується індивідуально.
Типова економія
Проєкти з чистою архітектурою потребують на 40-50% менше часу на підтримку та додавання нових фіч. Один клієнт після рефакторингу скоротив кількість багів у продакшені на 60%.







