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

Почему нельзя LocalContext.current вызывать в Use Case или репозитории?

1.8 Middle🔥 121 комментариев
#Архитектура и паттерны

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

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

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

Почему LocalContext.current нельзя вызывать в Use Case или Repository

Основная причина заключается в нарушении принципов чистой архитектуры (Clean Architecture) и разделения ответственности (Separation of Concerns), которые являются фундаментальными для создания масштабируемых, тестируемых и устойчивых к изменениям Android приложений.

Архитектурные принципы и слои

В современной Android разработке, особенно с использованием таких подходов как MVVM, MVI или Clean Architecture, принято разделять код на четкие слои:

  1. Presentation Layer (UI)Activity, Fragment, Compose UI, ViewModel.
  2. Domain Layer (Business Logic)Use Cases (Интеракторы).
  3. Data LayerRepository, DataSource (Local, Remote).

Ключевое правило: Domain и Data слои должны быть независимы от платформы и фреймворка. Они не должны знать о Android SDK, Context, Activity, Fragment или Jetpack Compose. Это позволяет:

  • Тестировать бизнес-логику и данные без эмулятора/устройства. Use Case и Repository можно тестировать с помощью обычных юнит-тестов (JUnit) на JVM.
  • Переносить логику на другие платформы (например, на серверную часть).
  • Изолировать изменения. Если Android SDK или Compose изменятся, это не затронет ядро бизнес-логики.
  • Предотвращать случайные зависимости. Context — это тяжелый, платформо-специфичный объект, который несет в себе множество зависимостей (ресурсы, системные сервисы, жизнь Activity).

LocalContext.current — это Compose-specific API, который предоставляет Context внутри области композиции (Composition). Его использование напрямую в Use Case или Repository мгновенно привязывает эти архитектурные слои к Android и Jetpack Compose, разрушая их независимость.

Проблемы прямого использования LocalContext.current в низких слоях

  1. Невозможность юнит-тестирования. Код, зависящий от LocalContext.current, невозможно запустить в тестовой среде JVM. Это нарушает базовый принцип тестирования.

    // Плохой пример: Use Case зависит от Context
    class BadUserUseCase {
        fun execute(): User {
            val context = LocalContext.current // ОШИБКА: доступ только в Composition!
            // ... логика, использующая context
        }
    }
    
    // Тест для такого UseCase НЕВОЗМОЖЕН в JUnit
    class BadUserUseCaseTest {
        @Test
        fun testExecute() {
            val useCase = BadUserUseCase()
            useCase.execute() // Упадет, так как нет Composition и LocalContext
        }
    }
    
  2. Смешение ответственности. Use Case должен отвечать только за бизнес-правила (например, "пользователь может совершить покупку, если у него достаточный баланс"). Он не должен знать, как получить доступ к файловой системе (context.filesDir) или запустить сервис. Repository должен абстрагировать источник данных ("получить пользователя из сети или базы"), но не реализовывать Android-специфичные операции для этого.

  3. Сложность и ненадежность получения Context. LocalContext.current доступен только внутри CompositionScope во время выполнения композиции. Вызов его вне этой области (например, внутри фонового корутин-вызова или инициализации класса) приведет к исключению.

    // Этот код приведет к ошибке при выполнении
    class MyRepository {
        // Инициализация происходит при создании объекта, вне Composition
        private val context = LocalContext.current // IllegalCompositionException!
    
        fun fetchData(): Flow<Data> {
            // Даже если вызвать здесь, это может быть вне композиции
            val ctx = LocalContext.current // Опасно и неправильно!
            return flow { ... }
        }
    }
    

Правильный подход: Dependency Injection (DI)

Правильный способ предоставить Context (или его специфичные части, например ApplicationContext) в нижние слои — использовать инъекцию зависимостей (Dependency Injection).

  • Context (обычно ApplicationContext) внедряется как зависимость через конструктор Repository.
  • Use Case получает все необходимые данные через свои параметры или зависимости (например, Repository), и не имеет прямого доступа к Context.
// Правильный пример с Dependency Injection (используем Hilt для примера)

// Data Layer: Repository получает Context через DI
@Singleton
class CorrectUserRepository @Inject constructor(
    private val appContext: ApplicationContext, // Внедряем ApplicationContext
    private val apiService: ApiService
) {
    fun getUser(id: String): User {
        // Repository может использовать context для кода, требующего Android,
        // например, чтения SharedPreferences или работы с Room DB.
        val preferences = appContext.getSharedPreferences("prefs", MODE_PRIVATE)
        // Но сама логика "получить пользователя" абстрагирована в Repository.
        return apiService.fetchUser(id)
    }
}

// Domain Layer: Use Case не знает о Context, только о Repository
class CorrectUserUseCase @Inject constructor(
    private val userRepository: CorrectUserRepository
) {
    suspend fun execute(userId: String): UserResult {
        // Здесь только бизнес-логика.
        val user = userRepository.getUser(userId)
        return if (user.isActive) {
            UserResult.Success(user)
        } else {
            UserResult.Error("User inactive")
        }
    }
}

// Presentation Layer: ViewModel и UI (Compose) вызывают Use Case
@HiltViewModel
class UserViewModel @Inject constructor(
    private val userUseCase: CorrectUserUseCase
) : ViewModel() {

    fun loadUser(id: String) {
        viewModelScope.launch {
            val result = userUseCase.execute(id)
            // Обновить UI State
        }
    }
}

// В Compose UI мы можем безопасно использовать LocalContext.current для UI-задач
@Composable
fun UserScreen(viewModel: UserViewModel) {
    val uiContext = LocalContext.current // Для UI: показ Toast, запуск Activity итд.
    // Но этот context НЕ передается в Use Case или Repository.
}

Заключение

Вызов LocalContext.current в Use Case или Repository является серьезной архитектурной ошибкой. Он нарушает инкапсуляцию, тестируемость и переносимость ключевых компонентов приложения. Вместо этого следует применять практики Clean Architecture и Dependency Injection, где платформо-специфичные ресурсы (как Context) предоставляются только тем слоям, которым они действительно нужны (Repository через DI), и никогда — слою бизнес-логики (Use Case). Это создает прочную основу для долгосрочной поддержки и развития проекта.

Почему нельзя LocalContext.current вызывать в Use Case или репозитории? | PrepBro