Утечки памяти в 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%.







