В Kotlin Coroutines Dispatcher определяет, на каком потоке выполняется код. Для работы с UI используют Dispatchers.Main, для сетевых или файловых операций — Dispatchers.IO, а для тяжёлых вычислений — Dispatchers.Default. При запросе данных корутина запускается на главном потоке, а сам сетевой или базовый запрос выполняется на IO-потоке через withContext(Dispatchers.IO). После получения данных они преобразуются в domain модель и возвращаются на главный поток для обновления интерфейса пользователя. Такой подход позволяет не блокировать UI и безопасно работать с данными.
class UserViewModel(private val repository: UserRepository) : ViewModel() {
val userLiveData = MutableLiveData<User>()
val errorLiveData = MutableLiveData<String>()
fun fetchUser() {
// Корутина запускается на главном потоке, чтобы можно было обновлять UI
viewModelScope.launch(Dispatchers.Main) {
try {
// Выполняем запрос в фоновом потоке IO
val user = withContext(Dispatchers.IO) {
val dto = repository.getUser() // получение DTO из API
repository.mapper.map(dto) // преобразуем в domain модель
}
// Возвращаем результат на Main поток и обновляем LiveData
userLiveData.value = user
} catch (e: Exception) {
// В случае ошибки обновляем LiveData с сообщением
errorLiveData.value = "Ошибка: ${e.message}"
}
}
}
}
val userLiveData = MutableLiveData<User>()
val errorLiveData = MutableLiveData<String>()
fun fetchUser() {
// Корутина запускается на главном потоке, чтобы можно было обновлять UI
viewModelScope.launch(Dispatchers.Main) {
try {
// Выполняем запрос в фоновом потоке IO
val user = withContext(Dispatchers.IO) {
val dto = repository.getUser() // получение DTO из API
repository.mapper.map(dto) // преобразуем в domain модель
}
// Возвращаем результат на Main поток и обновляем LiveData
userLiveData.value = user
} catch (e: Exception) {
// В случае ошибки обновляем LiveData с сообщением
errorLiveData.value = "Ошибка: ${e.message}"
}
}
}
}
Базовый Use Case в слое domain — это класс или функция, которая выполняет одну конкретную бизнес-операцию. Он работает только с domain моделями и не зависит от UI или конкретной реализации репозитория. Use Case получает данные через репозиторий, преобразует DTO в domain модель и возвращает готовый объект для использования в других слоях приложения, например, в ViewModel.
// Доменная модель
data class User(val id: Int, val name: String)
// Интерфейс репозитория (domain слой не зависит от реализации)
interface UserRepository {
suspend fun getUserById(id: Int): UserDTO
}
// Mapper: DTO -> Domain
fun UserDTO.toDomain(): User {
return User(id = this.id, name = this.username)
}
// Use Case: бизнес-логика — получение пользователя по ID
class GetUserUseCase(private val repository: UserRepository) {
suspend operator fun invoke(userId: Int): User {
val userDTO = repository.getUserById(userId) // получаем данные
return userDTO.toDomain() // преобразуем в domain модель
}
}
data class User(val id: Int, val name: String)
// Интерфейс репозитория (domain слой не зависит от реализации)
interface UserRepository {
suspend fun getUserById(id: Int): UserDTO
}
// Mapper: DTO -> Domain
fun UserDTO.toDomain(): User {
return User(id = this.id, name = this.username)
}
// Use Case: бизнес-логика — получение пользователя по ID
class GetUserUseCase(private val repository: UserRepository) {
suspend operator fun invoke(userId: Int): User {
val userDTO = repository.getUserById(userId) // получаем данные
return userDTO.toDomain() // преобразуем в domain модель
}
}
Для чего нужна чистая архитектура? За что отвечают слои data, domain, presentation? Какие элементы обязательно должны быть в этих слоях? Нужно привести пример из кода, и ответить своими словами. Можно перечислить обязательное содержимое каждого слоя
Чистая архитектура нужна, чтобы проект был структурированным и удобным для поддержки.
Слой data отвечает за получение и хранение данных (DTO, репозитории, API),
domain — за бизнес-логику и правила работы (доменные модели, Use Case),
а presentation — за отображение и взаимодействие с пользователем (ViewModel, экраны, LiveData).
Каждый слой содержит только свои элементы, чтобы код оставался чистым и легко изменяемым.
Слой data отвечает за получение и хранение данных (DTO, репозитории, API),
domain — за бизнес-логику и правила работы (доменные модели, Use Case),
а presentation — за отображение и взаимодействие с пользователем (ViewModel, экраны, LiveData).
Каждый слой содержит только свои элементы, чтобы код оставался чистым и легко изменяемым.
// ---------------- Domain слой ----------------
// Доменная модель
data class User(val id: Int, val name: String)
// Репозиторий интерфейс (domain не зависит от реализации)
interface UserRepository {
suspend fun getUserById(id: Int): UserDTO
}
// Use Case: получение пользователя
class GetUserUseCase(private val repository: UserRepository) {
suspend operator fun invoke(userId: Int): User =
repository.getUserById(userId).toDomain() // DTO -> Domain
}
// ---------------- Data слой ----------------
// DTO
data class UserDTO(val id: Int, val username: String)
// Mapper: DTO -> Domain
fun UserDTO.toDomain() = User(id, username)
// Реализация репозитория
class UserRepositoryImpl(private val api: ApiService) : UserRepository {
override suspend fun getUserById(id: Int) = api.fetchUser(id) // Получаем данные из API
}
// ---------------- Presentation слой ----------------
class UserViewModel(private val getUserUseCase: GetUserUseCase) : ViewModel() {
val userLiveData = MutableLiveData<User>()
fun loadUser(id: Int) {
viewModelScope.launch { // запускаем корутину
userLiveData.value = getUserUseCase(id) // получаем пользователя через Use Case
}
}
}
// Доменная модель
data class User(val id: Int, val name: String)
// Репозиторий интерфейс (domain не зависит от реализации)
interface UserRepository {
suspend fun getUserById(id: Int): UserDTO
}
// Use Case: получение пользователя
class GetUserUseCase(private val repository: UserRepository) {
suspend operator fun invoke(userId: Int): User =
repository.getUserById(userId).toDomain() // DTO -> Domain
}
// ---------------- Data слой ----------------
// DTO
data class UserDTO(val id: Int, val username: String)
// Mapper: DTO -> Domain
fun UserDTO.toDomain() = User(id, username)
// Реализация репозитория
class UserRepositoryImpl(private val api: ApiService) : UserRepository {
override suspend fun getUserById(id: Int) = api.fetchUser(id) // Получаем данные из API
}
// ---------------- Presentation слой ----------------
class UserViewModel(private val getUserUseCase: GetUserUseCase) : ViewModel() {
val userLiveData = MutableLiveData<User>()
fun loadUser(id: Int) {
viewModelScope.launch { // запускаем корутину
userLiveData.value = getUserUseCase(id) // получаем пользователя через Use Case
}
}
}
❤2💩1