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

Как использовать контекст в ViewModel?

2.3 Middle🔥 201 комментариев
#Android компоненты#Архитектура и паттерны

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

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

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

Использование Context в ViewModel: Правильные подходы и антипаттерны

Использование Context в ViewModel — это важный аспект архитектуры Android-приложений, требующий соблюдения определенных правил, чтобы избежать утечек памяти и нарушений принципов MVVM.

Почему напрямую передавать Context в ViewModel — плохая идея

ViewModel переживает изменения конфигурации (поворот экрана), а Activity Context — нет. Если сохранить ссылку на Activity Context в ViewModel, это приведет к утечке памяти, так как ViewModel будет удерживать ссылку на уничтоженную Activity.

// ❌ АНТИПАТТЕРН: Прямое хранение Context в ViewModel
class WrongViewModel(private val context: Context) : ViewModel() {
    fun showToast() {
        Toast.makeText(context, "Hello", Toast.LENGTH_SHORT).show()
    }
}

Правильные подходы для работы с Context в ViewModel

1. Использование Application Context через AndroidViewModel

Класс AndroidViewModel предоставляет доступ к Application Context, который живет в течение всего жизненного цикла приложения и не вызывает утечек.

// ✅ Правильный подход: Использование AndroidViewModel
class MyViewModel(application: Application) : AndroidViewModel(application) {
    
    private val appContext = getApplication<Application>().applicationContext
    
    fun loadDataFromAssets(): String {
        return try {
            appContext.assets.open("data.json")
                .bufferedReader()
                .use { it.readText() }
        } catch (e: Exception) {
            "Error loading data"
        }
    }
    
    fun getAppVersion(): String {
        val packageInfo = appContext.packageManager
            .getPackageInfo(appContext.packageName, 0)
        return packageInfo.versionName
    }
}

2. Передача ресурсов и зависимостей через параметры

Вместо передачи Context для доступа к ресурсам, передавайте сами ресурсы или абстракции:

// ✅ Правильный подход: Передача ресурсов
class UserViewModel(
    private val stringResources: StringResources,
    private val preferencesManager: PreferencesManager
) : ViewModel() {
    
    fun getUserGreeting(userName: String): String {
        return stringResources.getString(
            R.string.greeting_message, userName
        )
    }
}

// Абстракция для работы со строками
interface StringResources {
    fun getString(@StringRes resId: Int, vararg formatArgs: Any): String
}

// Реализация для Android
class AndroidStringResources(
    private val context: Context
) : StringResources {
    override fun getString(resId: Int, vararg formatArgs: Any): String {
        return context.getString(resId, *formatArgs)
    }
}

3. Использование LiveData/StateFlow для событий, требующих Context

Для операций, которые действительно требуют Activity Context (показ диалогов, Toast, запуск Activity), используйте события:

// ✅ Правильный подход: События через LiveData/StateFlow
class EventViewModel : ViewModel() {
    
    private val _events = MutableSharedFlow<UiEvent>()
    val events = _events.asSharedFlow()
    
    sealed class UiEvent {
        data class ShowToast(val message: String) : UiEvent()
        data class ShowDialog(val title: String, val message: String) : UiEvent()
        object NavigateToSettings : UiEvent()
    }
    
    fun performAction() {
        viewModelScope.launch {
            _events.emit(UiEvent.ShowToast("Action completed"))
        }
    }
}

// В Activity/Fragment
class MainActivity : AppCompatActivity() {
    private val viewModel: EventViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.events.collect { event ->
                    when (event) {
                        is EventViewModel.UiEvent.ShowToast -> {
                            Toast.makeText(
                                this@MainActivity, // Используем Activity Context здесь
                                event.message,
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                        // Обработка других событий
                    }
                }
            }
        }
    }
}

4. Внедрение зависимостей, требующих Context

Для зависимостей, которым нужен Context (SharedPreferences, WorkManager, Room), используйте Dependency Injection:

// ✅ Правильный подход: Внедрение зависимостей
class SettingsViewModel(
    private val preferences: AppPreferences,
    private val notificationManager: NotificationHelper
) : ViewModel() {
    
    fun saveSettings(settings: UserSettings) {
        viewModelScope.launch {
            preferences.saveSettings(settings)
            notificationManager.showSettingsSavedNotification()
        }
    }
}

// Модуль DI (Hilt пример)
@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    
    @Provides
    fun provideAppPreferences(@ApplicationContext context: Context): AppPreferences {
        return AppPreferences(context)
    }
    
    @Provides
    fun provideNotificationHelper(@ApplicationContext context: Context): NotificationHelper {
        return NotificationHelper(context)
    }
}

Практические рекомендации

  • Всегда предпочитайте Application Context через AndroidViewModel, если нужен только доступ к ресурсам приложения
  • Для UI-операций используйте паттерн событий, чтобы Context использовался только во View слое
  • Инкапсулируйте логику работы с Context в отдельные классы (репозитории, менеджеры)
  • Тестируемость: Отсутствие прямого Context в ViewModel упрощает unit-тестирование
  • Используйте Hilt или Koin для автоматического внедрения Application Context в зависимости

Пример комплексного использования

class ProductsViewModel(
    application: Application,
    private val productsRepository: ProductsRepository
) : AndroidViewModel(application) {
    
    private val appContext = getApplication<Application>().applicationContext
    
    private val _uiState = MutableStateFlow<ProductsUiState>(ProductsUiState.Loading)
    val uiState = _uiState.asStateFlow()
    
    fun loadProducts() {
        viewModelScope.launch {
            _uiState.value = ProductsUiState.Loading
            
            val result = productsRepository.getProducts()
            
            if (result.isSuccess) {
                _uiState.value = ProductsUiState.Success(result.getOrNull()!!)
            } else {
                val errorMessage = appContext.getString(
                    R.string.error_loading_products
                )
                _uiState.value = ProductsUiState.Error(errorMessage)
            }
        }
    }
}

sealed class ProductsUiState {
    object Loading : ProductsUiState()
    data class Success(val products: List<Product>) : ProductsUiState()
    data class Error(val message: String) : ProductsUiState()
}

Правильное использование Context в ViewModel — ключ к созданию стабильных, тестируемых и поддерживаемых Android-приложений, соответствующих современным архитектурным принципам.