Как сохранить данные из ViewModel для переиспользования
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Сохранение данных из ViewModel для переиспользования
В архитектуре Android приложений ViewModel играет ключевую роль в хранении и управлении UI-mданными, связанными с жизненным циклом активити или фрагмента. Для переиспользования данных между различными экранами, сессиями приложения или даже после завершения процесса необходимо использовать комбинацию подходов.
Основные стратегии сохранения данных
1. Сохранение в локальной базе данных
Наиболее надежный способ для структурных данных, которые должны сохраняться между сеансами приложения.
class ProductViewModel(
private val productRepository: ProductRepository
) : ViewModel() {
private val _products = MutableLiveData<List<Product>>()
val products: LiveData<List<Product>> = _products
fun loadProducts() {
viewModelScope.launch {
// Загрузка из базы данных
val cachedProducts = productRepository.getProductsFromDb()
_products.value = cachedProducts
// Обновление из сети с сохранением в БД
val freshProducts = productRepository.fetchProductsFromApi()
productRepository.saveProductsToDb(freshProducts)
_products.value = freshProducts
}
}
}
2. Использование SharedPreferences/DataStore
Для простых ключ-значение данных, таких как настройки пользователя, токены авторизации или флаги.
class SettingsViewModel(
private val dataStore: DataStore<Preferences>
) : ViewModel() {
val darkModeEnabled = dataStore.data
.map { preferences ->
preferences[PreferencesKeys.DARK_MODE] ?: false
}
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), false)
fun toggleDarkMode(enabled: Boolean) {
viewModelScope.launch {
dataStore.edit { preferences ->
preferences[PreferencesKeys.DARK_MODE] = enabled
}
}
}
}
object PreferencesKeys {
val DARK_MODE = booleanPreferencesKey("dark_mode_enabled")
}
3. Кэширование в памяти с Singleton-репозиторием
Для временного хранения данных, которые должны быть доступны разным ViewModel в пределах одного сеанса приложения.
object SessionCache {
private val userCache = ConcurrentHashMap<String, User>()
private val productCache = mutableListOf<Product>()
fun cacheUser(user: User) {
userCache[user.id] = user
}
fun getCachedUser(userId: String): User? {
return userCache[userId]
}
}
class UserViewModel : ViewModel() {
fun loadUser(userId: String): LiveData<User> {
return liveData {
// Проверка кэша в памяти
val cachedUser = SessionCache.getCachedUser(userId)
if (cachedUser != null) {
emit(cachedUser)
return@liveData
}
// Загрузка из других источников
val user = userRepository.loadUser(userId)
SessionCache.cacheUser(user)
emit(user)
}
}
}
Архитектурные рекомендации
- Разделение ответственности: ViewModel не должна напрямую работать с хранилищами. Используйте Repository pattern для абстрагирования источника данных.
- Единый источник истины (SSOT): Определите главный источник данных для каждого типа информации.
- Реактивное программирование: Используйте LiveData, StateFlow или SharedFlow для наблюдения за изменениями данных.
Практический пример с полной архитектурой
// Repository с многоуровневым кэшированием
class UserRepository(
private val userDao: UserDao,
private val apiService: ApiService,
private val cache: MemoryCache
) {
suspend fun getUser(userId: String): User {
// 1. Проверка памяти
cache.getUser(userId)?.let { return it }
//132. Проверка локальной БД
val localUser = userDao.getUserById(userId)
if (localUser != null) {
cache.saveUser(localUser)
return localUser
}
// 3. Загрузка из сети
val remoteUser = apiService.getUser(userId)
userDao.insertUser(remoteUser) // Сохранение в БД
cache.saveUser(remoteUser) // Сохранение в памяти
return remoteUser
}
}
// ViewModel, использующая репозиторий
class UserProfileViewModel(
private val userRepository: UserRepository
) : ViewModel() {
private val _userState = MutableStateFlow<UserState>(UserState.Loading)
val userState: StateFlow<UserState> = _userState
fun loadUserProfile(userId: String) {
viewModelScope.launch {
_userState.value = UserState.Loading
try {
val user = userRepository.getUser(userId)
_userState.value = UserState.Success(user)
} catch (e: Exception) {
_userState.value = UserState.Error(e.message ?: "Unknown error")
}
}
}
}
Ключевые принципы
- Соответствие данных жизненному циклу: Определите, как долго должны храниться данные
- Синхронизация между источниками: Реализуйте стратегию обновления кэшей
- Обработка ошибок: Предусмотрите fallback-механизмы при недоступности данных
- Тестируемость: Используйте dependency injection для замены источников данных в тестах
Выбор стратегии зависит от типа данных, требований к персистентности и архитектуры приложения. Для большинства современных Android-приложений рекомендуется комбинированный подход: Room для структурных данных, DataStore для настроек и in-memory кэш для временных данных сессии.