Как организуешь связь UI и репозитория в твоем проекте
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Архитектурный подход к связи UI и репозитория
В современных Android-проектах я организую связь между UI-слоем и репозиторием через архитектурные паттерны, которые обеспечивают разделение ответственности, тестируемость и управление жизненным циклом. Основной подход — это комбинация ViewModel из архитектурных компонентов Android и корутин/RxJava для асинхронных операций.
Ключевые компоненты архитектуры:
UI (Fragment/Activity) → ViewModel → UseCase/Interactor → Repository → Data Sources
Детальная реализация связи
1. ViewModel как посредник
ViewModel выступает мостом между UI и бизнес-логикой, переживая конфигурационные изменения:
class UserProfileViewModel(
private val getUserProfileUseCase: GetUserProfileUseCase
) : ViewModel() {
private val _userState = MutableStateFlow<UserState>(UserState.Loading)
val userState: StateFlow<UserState> = _userState.asStateFlow()
fun loadUserProfile(userId: String) {
viewModelScope.launch {
_userState.value = UserState.Loading
try {
val user = getUserProfileUseCase(userId)
_userState.value = UserState.Success(user)
} catch (e: Exception) {
_userState.value = UserState.Error(e.message)
}
}
}
}
2. Репозиторий как единый источник правды
Репозиторий абстрагирует источники данных и предоставляет чистый API:
class UserRepositoryImpl(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO
) : UserRepository {
override suspend fun getUserProfile(id: String): User {
return withContext(dispatcher) {
// Сначала проверяем локальные данные
val localUser = localDataSource.getUser(id)
if (localUser != null && !localUser.isStale()) {
return@withContext localUser
}
// Если данных нет или они устарели — запрашиваем с сервера
try {
val remoteUser = remoteDataSource.getUser(id)
localDataSource.saveUser(remoteUser)
remoteUser
} catch (e: Exception) {
// Fallback на локальные данные при ошибке сети
localUser ?: throw e
}
}
}
}
3. Наблюдение за состоянием в UI
UI компоненты наблюдают за StateFlow/LiveData из ViewModel:
class UserProfileFragment : Fragment() {
private val viewModel: UserProfileViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.userState.collect { state ->
when (state) {
is UserState.Loading -> showLoading()
is UserState.Success -> showUser(state.user)
is UserState.Error -> showError(state.message)
}
}
}
}
viewModel.loadUserProfile("user123")
}
}
Принципы организации связи
Разделение ответственности:
- UI Layer (Fragment/Activity): только отображение данных и обработка пользовательских событий
- ViewModel: подготовка данных для UI, обработка событий от UI
- Repository: координация данных из различных источников (база данных, сеть, кэш)
- Data Sources: непосредственная работа с конкретными источниками
Управление зависимостями:
Использую Dependency Injection (чаще всего Hilt) для управления зависимостями:
@Module
@InstallIn(ViewModelComponent::class)
object UserModule {
@Provides
fun provideUserRepository(
localDataSource: UserLocalDataSource,
remoteDataSource: UserRemoteDataSource
): UserRepository {
return UserRepositoryImpl(localDataSource, remoteDataSource)
}
}
Обработка состояний загрузки и ошибок:
Определяю sealed классы для представления различных состояний:
sealed class UserState {
object Loading : UserState()
data class Success(val user: User) : UserState()
data class Error(val message: String?) : UserState()
}
Тестируемость:
Такая организация позволяет легко тестировать каждый компонент изолированно:
- UI тесты с помощью Espresso и mock ViewModel
- ViewModel тесты с mock репозиториями
- Repository тесты с mock источниками данных
Преимущества такого подхода
- Чистая сепарация — UI не знает об источниках данных
- Сохранение состояния при смене конфигурации через ViewModel
- Простое тестирование каждого слоя независимо
- Гибкость в замене источников данных или бизнес-логики
- Эффективное управление жизненным циклом корутин через viewModelScope
Такой подход соответствует рекомендациям Android Architecture Guidelines и обеспечивает масштабируемость приложений даже в крупных проектах с множеством разработчиков.