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

Как получить Context в репозитории?

2.0 Middle🔥 171 комментариев
#Dependency Injection#Архитектура и паттерны

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

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

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

Получение Context в репозитории: архитектурные подходы и антипаттерны

Этот вопрос затрагивает фундаментальные принципы архитектуры Android-приложений. Прямое получение Context в репозитории, особенно через Application.getInstance() или статические поля, считается архитектурным антипаттерном. Репозиторий — это слой, отвечающий за управление данными, и его не следует завязывать на Android-специфичные зависимости, такие как Context. Это нарушает принцип единой ответственности, усложняет тестирование и создает скрытые зависимости.

Почему передача Context в репозиторий — плохая практика?

  • Нарушение чистой архитектуры: Репозиторий должен оставаться независимым от фреймворка Android. Получая Context, он становится привязанным к Android SDK.
  • Сложности с тестированием: Для юнит-тестов потребуется предоставлять mock или InstrumentationRegistry.getContext(), что превращает быстрые юнит-тесты в медленные инструментальные.
  • Неявные зависимости: Усложняется понимание того, откуда репозиторий берет зависимости, что ведет к хрупкости кода.
  • Утечки контекста: Риск неправильного использования Activity Context вместо Application Context, что может привести к утечкам памяти.

Правильные подходы (от лучшего к допустимому)

1. Внедрение зависимостей через параметры (Рекомендуемый способ)

Лучший метод — передавать в репозиторий не сам Context, а только необходимые данные или интерфейсы, которые уже получены с его помощью во ViewModel или UseCase.

// Неправильно: репозиторий сам берет контекст
class BadUserRepository {
    fun getPrefsData(): String {
        val prefs = PreferenceManager.getDefaultSharedPreferences(MyApp.context)
        return prefs.getString("key", "") ?: ""
    }
}

// Правильно: зависимости передаются явно через конструктор
class GoodUserRepository(
    private val userLocalDataSource: UserLocalDataSource, // Источник данных, уже имеющий доступ к контексту если нужно
    private val apiService: ApiService
) {
    suspend fun getUser(): User {
        // Логика получения пользователя из локального источника или сети
        return userLocalDataSource.getUser() ?: apiService.fetchUser()
    }
}

// DataSource, живущий в data-слое, может получать Context через DI (например, Hilt)
@Singleton
class UserLocalDataSource @Inject constructor(
    @ApplicationContext private val appContext: Context
) {
    private val prefs = appContext.getSharedPreferences("user_prefs", Context.MODE_PRIVATE)

    fun saveUserToken(token: String) {
        prefs.edit().putString("token", token).apply()
    }
}

2. Использование Dependency Injection (DI) с библиотеками (Hilt/Dagger)

Современный стандарт. Контекст внедряется в те классы, которым он действительно нужен (например, в DataSource), а репозиторий получает уже готовые зависимости.

// Модуль в Hilt для предоставления контекста приложения
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Provides
    @Singleton
    fun provideApplicationContext(@ApplicationContext appContext: Context): Context {
        return appContext
    }

    @Provides
    @Singleton
    fun provideUserLocalDataSource(appContext: Context): UserLocalDataSource {
        return UserLocalDataSource(appContext)
    }
}

// Репозиторий, получающий зависимости через конструктор
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) {
    // ... логика репозитория
}

3. Передача Context как параметра методов (Ограниченное использование)

Допустимо в некоторых случаях, но часто указывает на проблему в дизайне. Может быть оправдано для одноразовых операций.

interface FileRepository {
    fun saveFile(context: Context, fileData: ByteArray): Boolean
}

class FileRepositoryImpl : FileRepository {
    override fun saveFile(context: Context, fileData: ByteArray): Boolean {
        return try {
            context.openFileOutput("data.dat", Context.MODE_PRIVATE).use {
                it.write(fileData)
            }
            true
        } catch (e: Exception) {
            false
        }
    }
}

// Использование в ViewModel или UseCase
viewModelScope.launch {
    val isSaved = fileRepository.saveFile(applicationContext, data)
}

Краткий итог и рекомендации

  • Идеально: Репозиторий не знает о Context. Все необходимые ресурсы (датасорсы, клиенты) передаются ему через конструктор с помощью Dependency Injection.
  • Допустимо: Явная передача Context как параметра в метод, если это оправдано и не нарушает тестируемость.
  • Неприемлемо: Прямой доступ к Context через статические методы, синглтоны или неявное внедрение.

Правильный выбор подхода делает код тестируемым, поддерживаемым и соответствующим принципам SOLID и Clean Architecture. В современных проектах с Hilt проблема получения Context в репозитории практически не возникает, так как DI-фреймворк берет на себя управление этими зависимостями.

Как получить Context в репозитории? | PrepBro