Как сохранить состояние экрана с Jetpack Compose
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Сохранение состояния экрана в Jetpack Compose
В Jetpack Compose управление состоянием является фундаментальной концепцией. Сохранение состояния экрана (например, при повороте устройства или переходе между процессами) достигается через комбинацию архитектурных подходов и специфичных API Compose. Вот основные стратегии.
1. Использование remember и rememberSaveable
Для сохранения простого состояния внутри Composition (и его восстановления после конфигурационных изменений, например, поворота) используется rememberSaveable.
remember сохраняет состояние только в течение текущей композиции. rememberSaveable автоматически сохраняет состояние в Bundle (подобно onSaveInstanceState в View-системе) и восстанавливает его после рекомпозиции из-за конфигурационных изменений.
@Composable
fun CounterScreen() {
// Состояние сохранится при повороте
var count by rememberSaveable { mutableStateOf(0) }
Column {
Text("Count: $count")
Button(onClick = { count++ }) {
Text("Increment")
}
}
}
rememberSaveable работает с типами данных, которые можно автоматически сохранить в Bundle (String, Int, Boolean и др.). Для сложных объектов нужно использовать механизмы Saver.
data class User(val name: String, val age: Int)
val CustomUserSaver = listSaver<User, Any>(
save = { user -> listOf(user.name, user.age) },
restore = { restored -> User(restored[0] as String, restored[1] as Int) }
)
@Composable
fun UserProfile() {
var userState by rememberSaveable(stateSaver = CustomUserSaver) {
mutableStateOf(User("Alice", 30))
}
}
2. Интеграция с архитектурными компонентами (ViewModel)
Для сохранения состояния, связанного с бизнес-логикой, которое должно жить дольше жизненного цикла UI (например, при переходе между экранами или после смерти процесса), используется ViewModel. Состояние в ViewModel не уничтожается при конфигурационных изменениях.
class MainViewModel : ViewModel() {
private val _uiState = mutableStateOf(MainUiState())
val uiState: State<MainUiState> = _uiState
fun updateData(newData: String) {
_uiState.value = _uiState.value.copy(data = newData)
}
}
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsState()
Column {
Text(text = uiState.data)
Button(onClick = { viewModel.updateData("New Data") }) {
Text("Update")
}
}
}
Для сохранения состояния ViewModel при смерти процесса (true process death) используется SavedStateHandle внутри ViewModel.
class PersistentViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// Сохраняем ключ в SavedStateHandle
var importantData by mutableStateOf(
savedStateHandle.get<String>("data") ?: "Default"
)
private set
fun saveData(value: String) {
importantData = value
savedStateHandle["data"] = value // Сохраняется в Bundle
}
}
3. Сохранение состояния Navigation
При использовании Jetpack Navigation Compose, состояние экрана (например, аргументы пути) можно сохранять через NavBackStackEntry и его SavedStateHandle.
// Установка аргумента при переходе
navController.navigate("details/$itemId")
// В composable экрана Details
@Composable
fun DetailsScreen(navBackStackEntry: NavBackStackEntry) {
val itemId = navBackStackEntry.arguments?.getString("itemId")
// Использование savedStateHandle экрана
val screenState = navBackStackEntry.savedStateHandle
var tempData by rememberSaveable { mutableStateOf(screenState.get<String>("temp") ?: "") }
LaunchedEffect(tempData) {
screenState["temp"] = tempData // Сохраняется для этого экрана
}
}
4. Сохранение состояния в локальное хранилище (Persistent Storage)
Для долгосрочного сохранения (между сессиями приложения) данные необходимо сохранять в персистентное хранилище: Room (база данных), DataStore (Preferences или Proto), файлы или SharedPreferences.
// Пример с Preferences DataStore
@Composable
fun SettingsScreen(dataStore: DataStore<Preferences> = preferencesDataStore) {
val savedValue = remember {
dataStore.data.map { prefs ->
prefs[stringPreferencesKey("setting_key")] ?: "default"
}.stateIn(
scope = rememberCoroutineScope(),
started = SharingStarted.WhileSubscribed(5000),
initialValue = "default"
)
}.collectAsState()
// Использование savedValue.value
}
Ключевые практики и рекомендации
- Разделение ответственности: UI-состояние (скролл позиция, текст поля) — в
rememberSaveable; бизнес-данные — вViewModel; персистентные данные — в репозиториях с локальным хранилищем. - Восстановление после смерти процесса: Для критичных данных комбинируйте
ViewModel+SavedStateHandle+ персистентное хранилище. ViewModel может восстановить из SavedStateHandle, а далее — из базы данных. - Состояние списков и LazyColumn: Для сохранения позиции скролла используйте
LazyListStateсrememberSaveable.val listState = rememberSaveable(saver = LazyListState.Saver) { LazyListState(firstVisibleItemIndex = 0, firstVisibleItemScrollOffset = 0) } - Тестирование: Проверяйте восстановление состояния при эмуляции конфигурационных изменений и смерти процесса (используйте "Don't keep activities" в настройках разработчика).
Таким образом, сохранение состояния в Compose — это многоуровневая система: от временного UI-состояния через rememberSaveable, до устойчивого бизнес-состояния в ViewModel с SavedStateHandle, и до постоянного хранения в локальной базе данных или DataStore. Правильный выбор уровня зависит от того, как долго и при каких условиях должно жить ваше состояние.