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

Как сохранить состояние экрана с Jetpack Compose

1.7 Middle🔥 141 комментариев
#UI и вёрстка#Архитектура и паттерны

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

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

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

Сохранение состояния экрана в 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. Правильный выбор уровня зависит от того, как долго и при каких условиях должно жить ваше состояние.

Как сохранить состояние экрана с Jetpack Compose | PrepBro