Как доставляешь репозиторий в Viewmodel
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Подход к доставке репозитория во ViewModel в Android приложении
При разработке приложений с использованием Clean Architecture или MVVM, доставка репозитория во ViewModel — критически важный процесс для обеспечения тестируемости, гибкости и соблюдения принципа инверсии зависимостей.
Основные методы внедрения зависимостей
1. Через конструктор ViewModel (наиболее предпочтительный)
Наиболее чистый и тестируемый способ — передача зависимости через конструктор ViewModel с использованием Dependency Injection (DI) фреймворков.
class MyViewModel(
private val myRepository: MyRepository
) : ViewModel() {
fun loadData() {
viewModelScope.launch {
val data = myRepository.getData()
// Обработка данных
}
}
}
Для внедрения через Dagger/Hilt:
@HiltViewModel
class MyViewModel @Inject constructor(
private val myRepository: MyRepository
) : ViewModel() {
// ViewModel логика
}
2. Использование фабрики ViewModelProvider.Factory
При необходимости передавать параметры или использовать внедрение зависимостей без аннотаций:
class MyViewModelFactory(
private val myRepository: MyRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MyViewModel(myRepository) as T
}
}
Использование в Activity/Fragment:
val factory = MyViewModelFactory(repository)
val viewModel = ViewModelProvider(this, factory).get(MyViewModel::class.java)
Архитектурные подходы и лучшие практики
Принципы, которых я придерживаюсь:
- Инверсия управления — ViewModel зависит от абстракции (интерфейса репозитория), а не от конкретной реализации:
interface UserRepository {
suspend fun getUser(id: String): User
}
class UserViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel()
- Ленивая инициализация при необходимости:
class MyViewModel @Inject constructor(
private val repositoryProvider: Provider<MyRepository>
) : ViewModel() {
private val repository by lazy { repositoryProvider.get() }
}
- Scoped зависимости с использованием Dagger/Hilt для правильного жизненного цикла:
@InstallIn(ViewModelComponent::class)
@Module
object ViewModelModule {
@Provides
fun provideRepository(
apiService: ApiService,
localDataSource: LocalDataSource
): MyRepository {
return MyRepositoryImpl(apiService, localDataSource)
}
}
Пример полной реализации с Dagger/Hilt:
// 1. Интерфейс репозитория
interface DataRepository {
suspend fun fetchData(): List<Data>
}
// 2. Реализация репозитория
class DataRepositoryImpl @Inject constructor(
private val apiService: ApiService,
private val cache: DataCache
) : DataRepository {
override suspend fun fetchData() = apiService.getData()
}
// 3. ViewModel с внедренной зависимостью
@HiltViewModel
class DataViewModel @Inject constructor(
private val repository: DataRepository
) : ViewModel() {
private val _data = MutableStateFlow<List<Data>>(emptyList())
val data: StateFlow<List<Data>> = _data.asStateFlow()
fun loadData() {
viewModelScope.launch {
_data.value = repository.fetchData()
}
}
}
Почему именно такой подход?
- Тестируемость — можно легко подменять реальный репозиторий моком в unit-тестах
- Гибкость — изменение реализации репозитория не затрагивает ViewModel
- Соблюдение SOLID — принцип инверсии зависимостей и открытости/закрытости
- Управление жизненным циклом — зависимости живут столько же, сколько и ViewModel
Альтернативные подходы (менее предпочтительные)
- Service Locator через Application класс — нарушает инверсию зависимостей
- Создание репозитория напрямую в ViewModel — усложняет тестирование и нарушает DI
- Singleton-репозитории — могут вызывать проблемы с памятью и жизненным циклом
Ключевой вывод: Использование dependency injection через конструктор ViewModel с поддержкой DI фреймворка — наиболее профессиональный и поддерживаемый подход в современной Android разработке. Это обеспечивает чистоту архитектуры, простоту тестирования и масштабируемость приложения.