상태가 호이스팅된 위치와 필요한 로직에 따라 서로 다른 API를 사용하여 UI 상태를 저장하고 복원할 수 있습니다. 모든 앱은 이를 가장 효과적으로 달성하기 위한 API 조합을 사용합니다.
모든 Android 앱은 활동 또는 프로세스 재생성으로 인해 UI 상태가 손실될 수 있습니다. 이러한 상태 손실은 다음과 같은 이벤트로 인해 발생할 수 있습니다.
- 구성 변경. 구성 변경이 수동으로 처리되지 않는 한 활동이 소멸되고 재생성됩니다.
- 시스템에서 시작된 프로세스 종료. 앱이 백그라운드에 있고 기기가 리소스(메모리 등)를 다른 프로세스에서 사용할 수 있도록 확보합니다.
이러한 이벤트 후에 상태를 보존하는 것은 긍정적인 사용자 경험을 제공하는 데 있어 중요합니다. 어느 상태가 보존되도록 선택해야 하는지는 앱의 고유한 사용자 흐름에 따라 달라집니다. 권장사항은 적어도 사용자 입력 및 탐색 관련 상태는 유지하는 것입니다. 목록의 스크롤 위치, 사용자가 더 자세히 알고자 하는 항목의 ID, 진행 중인 사용자 환경설정 선택, 텍스트 필드의 입력 등을 예로 들 수 있습니다.
이 페이지에서는 상태가 호이스팅되는 대상 위치와 상태를 필요로 하는 로직에 따라 UI 상태를 저장하는 데 사용할 수 있는 API를 요약합니다.
UI 로직
상태가 UI에서 구성 가능한 함수나 컴포지션으로 범위가 지정된 일반 상태 홀더 클래스로 호이스팅되는 경우 rememberSaveable
을 사용하여 여러 활동에서, 그리고 프로세스 재생성 후에도 상태를 유지할 수 있습니다.
다음 스니펫에서 rememberSaveable
은 단일 불리언 UI 요소 상태를 저장하는 데 사용됩니다.
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } }
showDetails
은 채팅 풍선이 접혔는지 아니면 펼쳐졌는지를 저장하는 불리언 변수입니다.
rememberSaveable
은 저장된 인스턴스 상태 메커니즘을 통해 UI 요소 상태를 Bundle
에 저장합니다.
기본 유형을 자동으로 번들에 저장할 수 있습니다. 상태가 데이터 클래스와 같이 기본 유형이 아닌 유형에 저장되어 있다면 Parcelize
주석을 사용하거나, listSaver
및 mapSaver
등의 Compose API를 사용하거나, Compose 런타임 Saver
클래스를 확장하여 맞춤 Saver 클래스를 구현하는 등의 다른 저장 메커니즘을 사용할 수 있습니다. 이러한 메서드에 관한 자세한 내용은 상태 저장 방법 문서를 참고하세요.
다음 스니펫에서 rememberLazyListState
Compose API는 rememberSaveable
을 사용하여 LazyColumn
또는 LazyRow
의 스크롤 상태로 구성되는 LazyListState
를 저장합니다. 또한 스크롤 상태를 저장하고 복원할 수 있는 맞춤 Saver인 LazyListState.Saver
를 사용합니다. 활동 또는 프로세스가 재생성된 후(예: 기기 방향 변경과 같은 구성 변경 후) 스크롤 상태가 보존됩니다.
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
권장사항
rememberSaveable
은 Bundle
을 사용하여 UI 상태를 저장합니다. Bundle은 활동에서 이루어지는 onSaveInstanceState()
호출과 같이 Bundle에 쓰기를 수행하기도 하는 다른 API와 공유됩니다. 단, 이 Bundle
의 크기는 제한적이므로 여기에 큰 객체를 저장하면 런타임에서 TransactionTooLarge
예외가 발생할 수 있습니다. 특히 앱 전체에서 동일한 Bundle
이 사용되는 단일 Activity
앱에서 문제가 될 수 있습니다.
이러한 유형의 비정상 종료를 방지하려면 번들에 크고 복잡한 객체나 객체 목록을 저장하지 않아야 합니다.
대신 ID나 키와 같이 필요한 최소 상태를 저장하고 더 복잡한 UI 상태의 복원을 영구 저장소와 같은 다른 메커니즘에 위임하는 데 이 데이터를 사용합니다.
이러한 디자인 옵션은 앱의 구체적인 사용 사례와 사용자가 앱에 기대하는 동작 방식에 따라 달라집니다.
상태 복원 확인
활동 또는 프로세스가 재생성되었을 때 rememberSaveable
을 사용하여 Compose 요소에 저장된 상태가 올바르게 복원되는지 확인할 수 있습니다. 이를 위한 API가 있습니다(예: StateRestorationTester
). 자세한 내용은 테스트 문서를 참고하세요.
비즈니스 로직
UI 요소 상태가 비즈니스 로직에서 필요하기 때문에 ViewModel
로 호이스팅된 경우 ViewModel
의 API를 사용할 수 있습니다.
Android 애플리케이션에서 ViewModel
을 사용하는 것의 주요 이점 중 하나는 구성 변경을 무료로 처리한다는 것입니다. 구성 변경이 발생하여 활동이 소멸되었다가 재생성된 경우 ViewModel
로 호이스팅된 UI 상태는 메모리에 유지됩니다. 재생성 후에는 기존 ViewModel
인스턴스가 새 활동 인스턴스에 연결됩니다.
그러나 ViewModel
인스턴스는 시스템에서 시작된 프로세스 종료가 발생한 경우에는 유지되지 않습니다.
UI 상태가 유지되도록 하려면 SavedStateHandle
API를 포함하는 ViewModel의 저장된 상태 모듈을 사용하세요.
권장사항
SavedStateHandle
은 UI 상태를 저장하기 위해 Bundle
메커니즘도 사용하므로 간단한 UI 요소 상태를 저장하는 데만 사용해야 합니다.
비즈니스 규칙을 적용하고 UI 이외의 애플리케이션 레이어에 액세스함으로써 생성되는 화면 UI 상태는 그 복잡도와 크기 때문에 SavedStateHandle
에 저장해서는 안 됩니다. 복잡하거나 큰 데이터를 저장할 때는 로컬 영구 스토리지를 비롯한 여러 메커니즘을 사용할 수 있습니다. 프로세스가 재생성된 후에는 SavedStateHandle
에 저장되었던 복원된 임시 상태(있는 경우)를 사용하여 화면이 재생성되고 화면 UI 상태가 데이터 영역으로부터 다시 생성됩니다.
SavedStateHandle
API
SavedStateHandle
에는 UI 요소 상태를 저장하는 여러 API가 있습니다. 그중에서도 다음 API가 중요합니다.
Compose State |
saveable() |
---|---|
StateFlow |
getStateFlow() |
Compose State
SavedStateHandle
의 saveable
API를 사용하여 UI 요소 상태를 MutableState
로 읽고 씁니다. 그러면 여러 활동에서, 그리고 프로세스 재생성 후에도 최소한의 코드 설정으로 UI 요소 상태가 유지됩니다.
saveable
API는 추가 설정 없이 기본 유형을 지원하며, rememberSaveable()
처럼 맞춤 Saver를 사용하기 위해 stateSaver
매개변수를 받습니다.
다음 스니펫에서 message
는 사용자 입력 유형을 TextField
에 저장합니다.
class ConversationViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } /*...*/ } val viewModel = ConversationViewModel(SavedStateHandle()) @Composable fun UserInput(/*...*/) { TextField( value = viewModel.message, onValueChange = { viewModel.update(it) } ) }
saveable
API 사용에 관한 자세한 내용은 SavedStateHandle
문서를 참고하세요.
StateFlow
getStateFlow()
를 사용하여 UI 요소 상태를 저장하고 SavedStateHandle
에서의 흐름으로 사용합니다. StateFlow
는 읽기 전용이며, 이 API에서는 개발자가 키를 지정해야 흐름을 대체하여 새 값을 내보낼 수 있습니다. 키를 구성했으면 StateFlow
를 검색하여 최신 값을 수집할 수 있습니다.
다음 스니펫에서 savedFilterType
은 채팅 앱의 채팅 채널 목록에 적용된 필터 유형을 저장하는 StateFlow
변수입니다.
private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey" class ChannelViewModel( channelsRepository: ChannelsRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow( key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS ) private val filteredChannels: Flow<List<Channel>> = combine(channelsRepository.getAll(), savedFilterType) { channels, type -> filter(channels, type) }.onStart { emit(emptyList()) } fun setFiltering(requestType: ChannelsFilterType) { savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType } /*...*/ } enum class ChannelsFilterType { ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS }
사용자가 새 필터 유형을 선택할 때마다 setFiltering
이 호출됩니다. 이렇게 하면 SavedStateHandle
에 키 _CHANNEL_FILTER_SAVED_STATE_KEY_
와 함께 새 값이 저장됩니다. savedFilterType
은 키에 저장된 최신 값을 내보내는 흐름입니다. filteredChannels
는 채널 필터링을 수행하기 위해 흐름을 수신합니다.
getStateFlow()
API에 관한 자세한 내용은 SavedStateHandle
문서를 참고하세요.
요약
다음 표에는 이 섹션에서 다룬 API와 각 API를 사용하여 UI 상태를 저장해야 하는 경우가 요약되어 있습니다.
이벤트 | UI 로직 | ViewModel 의 비즈니스 로직 |
---|---|---|
구성 변경 | rememberSaveable |
자동 |
시스템에서 시작된 프로세스 종료 | rememberSaveable |
SavedStateHandle |
사용할 API는 상태가 저장된 위치와 상태를 필요로 하는 로직에 따라 달라집니다. UI 로직에서 사용되는 상태의 경우 rememberSaveable
을 사용합니다. 비즈니스 로직에서 사용되는 상태의 경우 ViewModel
에 저장한다면 SavedStateHandle
을 사용하여 저장합니다.
번들 API(rememberSaveable
및 SavedStateHandle
)는 적은 양의 UI 상태를 저장하는 데 사용해야 합니다. 이 데이터는 다른 저장 메커니즘과 함께 UI를 이전 상태로 복원하는 데 필요한 최소한의 데이터입니다. 예를 들어 사용자가 번들에서 보고 있는 프로필의 ID를 저장하면 프로필 세부정보와 같은 대용량 데이터는 데이터 영역에서 가져올 수 있습니다.
UI 상태를 저장하는 다양한 방법에 관한 자세한 내용은 아키텍처 가이드의 UI 상태 저장 문서 및 데이터 영역 페이지를 참고하세요.
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- 상태를 호이스팅할 위치
- 상태 및 Jetpack Compose
- 목록 및 그리드