Как использовать контекст в ViewModel?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование 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-приложений, соответствующих современным архитектурным принципам.