← Назад к вопросам

Как организуешь связь UI и репозитория в твоем проекте

2.0 Middle🔥 252 комментариев
#Архитектура и паттерны#Работа с данными

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Архитектурный подход к связи 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 источниками данных

Преимущества такого подхода

  1. Чистая сепарация — UI не знает об источниках данных
  2. Сохранение состояния при смене конфигурации через ViewModel
  3. Простое тестирование каждого слоя независимо
  4. Гибкость в замене источников данных или бизнес-логики
  5. Эффективное управление жизненным циклом корутин через viewModelScope

Такой подход соответствует рекомендациям Android Architecture Guidelines и обеспечивает масштабируемость приложений даже в крупных проектах с множеством разработчиков.