ViewModel의 저장된 상태 모듈 Android Jetpack의 구성요소
UI 상태 저장에서 언급했듯이 ViewModel 객체가
구성 변경사항을 처리할 수 있으므로 개발자는 회전
이나 다른 상황에서 상태에 신경 쓸 필요가 없습니다. 그러나 시스템에서 시작된 프로세스 종료를 처리해야 하는 경우 SavedStateHandle API를 백업으로 사용하는 것이 좋습니다.
일반적으로 UI 상태는 ViewModel 객체에 저장되거나 참조됩니다. 따라서 Compose에서
rememberSaveable을 사용하기 위해서는
저장된 상태 모듈이 개발자를 대신해 처리할 수 있는 상용구가 필요합니다.
이 모듈을 사용하면 ViewModel 객체는 생성자를 통해 SavedStateHandle
객체를 수신합니다. 이 객체는 저장된 상태에 객체를 작성하고 저장된 상태에서 객체를 검색할 수 있게 하는 키-값 맵입니다. 이러한 값은 시스템에서 프로세스가 중단된 후에도 유지되며 동일한 객체를 통해 계속 사용할 수 있습니다.
저장된 상태는 작업 스택에 연결됩니다. 작업 스택이 사라지면 저장된 상태도 사라집니다. 이는 앱을 강제 종료하거나 최근 메뉴에서 앱을 삭제하거나 기기를 재부팅할 때 발생할 수 있습니다. 이러한 경우 작업 스택이 사라지고 저장된 상태의 정보를 복원할 수 없습니다. 사용자가 시작한 UI 상태 닫기 시나리오에서는 저장된 상태가 복원되지 않습니다. 시스템에서 시작한 시나리오에서는 복원됩니다.
복잡하거나 큰 데이터의 경우 로컬 지속성을 사용해야 합니다.설정
SavedStateHandle을 사용하려면 ViewModel에 생성자 인수로 허용하세요.
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
그러면 추가 구성 없이 컴포저블 내에서 ViewModel의 인스턴스를 가져올 수 있습니다. 기본 ViewModel 팩터리는 ViewModel에 적절한 SavedStateHandle을 제공합니다.
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
맞춤 ViewModelProvider.Factory 인스턴스를 제공할 때
SavedStateHandle 사용을
설정할 수 있습니다.CreationExtrasviewModelFactory
SavedStateHandle을 사용한 작업
SavedStateHandle 클래스는 set() 메서드와 get() 메서드를 통해 저장된 상태에 데이터를 작성하고 저장된 상태에서 데이터를 검색할 수 있게 하는 키-값 맵입니다.
SavedStateHandle을 사용하면 쿼리 값이 프로세스 종료 전반에 유지되어 활동이나 프래그먼트에서 값을 수동으로 저장 및 복원하고 ViewModel에 다시 전달하지 않고도 재생성 전과 후에 동일한 필터링된 데이터 세트가 사용자에게 표시됩니다.
SavedStateHandle은(는) 호스트 Activity이(가) 중지된 경우(예: 앱이 백그라운드로 전송된 경우)에만 작성된 데이터를 저장합니다.
Activity가 중지된 동안 SavedStateHandle에 대한 쓰기는 저장되지 않습니다. 단, Activity가 onStart, onStop을 차례로 다시 수신하는 경우는 예외입니다 (예: 앱이 포그라운드로 전환된 후 다시 백그라운드로 전환된 경우).SavedStateHandle 에는 키-값 맵과 상호작용할 때 예상되는 다른 메서드도 있습니다.
contains(String key)- 지정된 키의 값이 있는지 확인합니다.remove(String key)- 지정된 키의 값을 삭제합니다.keys()-SavedStateHandle에 포함된 모든 키를 반환합니다.
또한 관찰 가능한 데이터 홀더를 사용하여 SavedStateHandle에서 값을 가져올 수 있습니다. 지원되는 유형 목록은 다음과 같습니다.
get() 및 set() 메서드와 동일한 메커니즘을 사용하여 저장되고 복원됩니다. Compose에서는 애플리케이션이 백그라운드로 전환될 때만 상태가 캡처됩니다. 즉, 앱이 백그라운드에 있는 동안 SavedStateHandle에서 관찰 가능한 데이터 홀더를 계속 업데이트할 수 있지만 앱 프로세스가 포그라운드로 전환되기 전에 종료되면 모든 상태 업데이트가 손실될 수도 있습니다.StateFlow
StateFlow 관측 가능 항목으로 래핑된 SavedStateHandle에서 값을 가져올 수 있습니다. 값을 직접 변경해야 하는지 여부에 따라 읽기 전용 스트림 또는 변경 가능한 스트림 중에서 선택할 수 있습니다.
getStateFlow(): 상태를 읽기만 해야 하는 경우에 사용합니다.SavedStateHandle의 다른 위치에서 키의 값을 업데이트하면 StateFlow가 새 값을 수신합니다. 읽기 전용 스트림을 노출하고 Flow 연산자를 사용하여 변환하려는 경우에 적합합니다.getMutableStateFlow(): 읽기 및 쓰기 액세스 권한이 모두 필요한 경우에 사용합니다. 반환된MutableStateFlow의.value를 업데이트하면 기본SavedStateHandle이 자동으로 업데이트되므로 키를 수동으로 설정할 필요가 없습니다.
대부분의 경우 데이터 목록을 필터링하기 위해 쿼리를 입력하는 등의 사용자 상호작용으로 인해 이러한 값이 업데이트됩니다.
getMutableStateFlow 지원은 SavedStateHandle에 추가되었습니다.
수명 주기 버전 2.9.0.class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { // Use getMutableStateFlow to read and write the query directly private val _query = savedStateHandle.getMutableStateFlow("query", "") val query: StateFlow= _query.asStateFlow() // Use getStateFlow if you only need a read-only stream to react to changes val filteredData: StateFlow<List > = query.flatMapLatest { repository.getFilteredData(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun setQuery(newQuery: String) { // Updating the MutableStateFlow automatically updates the SavedStateHandle _query.value = newQuery } }
KotlinX 직렬화 지원
복잡한 UI 상태의 경우 KotlinX 직렬화와 함께 saved 속성 위임을 사용할 수 있습니다. 이 위임을 사용하면 맞춤 @Serializable 데이터 클래스를 SavedStateHandle에 직접 유지할 수 있습니다. 이렇게 하면 프로세스 종료 전반에서 ViewModel의 상태가 유지되므로 Compose UI가 재생성 시 상태를 원활하게 복원할 수 있습니다.
사용하려면 데이터 클래스에 @Serializable을 주석 처리하고 ViewModel에서 saved 위임을 사용하세요.
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel // Ensure you have the savedstate-ktx dependency import androidx.savedstate.serialization.saved import kotlinx.serialization.Serializable @Serializable data class UserFilterState( val searchQuery: String, val minAge: Int, val includeInactive: Boolean ) class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { // The state is automatically serialized to a Bundle on process death, // and deserialized upon recreation. var filterState by savedStateHandle.saved { UserFilterState(searchQuery = "", minAge = 18, includeInactive = false) } fun updateQuery(newQuery: String) { // Mutating the property automatically updates the underlying SavedStateHandle filterState = filterState.copy(searchQuery = newQuery) } }
saved 위임은 지연 평가됩니다. 속성에 처음 액세스할 때까지 초기화 람다를 호출하거나 SavedStateHandle에 아무것도 저장하지 않습니다.Compose 상태 지원
상태가 KotlinX
직렬화 대신 Compose's Saver API에 의존하는 경우 lifecycle-viewmodel-compose 아티팩트가
saveable 위임을 제공합니다. 이렇게 하면
SavedStateHandle과 Compose의 Saver 간의 상호 운용성이 허용되므로 맞춤 Saver로 rememberSaveable을 통해 저장할 수 있는 모든 State는
SavedStateHandle을 사용해서도 저장할 수 있습니다.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
지원되는 유형
SavedStateHandle 내에 보관된 데이터는 앱의 나머지 savedInstanceState와 함께 Bundle,
로 저장되고 복원됩니다.
직접 지원되는 유형
기본적으로 다음과 같이 Bundle과 동일한 데이터 유형에 관해 SavedStateHandle에서 set() 및 get()을 호출할 수 있습니다.
| 유형/클래스 지원 | 배열 지원 |
double |
double[] |
int |
int[] |
long |
long[] |
String |
String[] |
byte |
byte[] |
char |
char[] |
CharSequence |
CharSequence[] |
float |
float[] |
Parcelable |
Parcelable[] |
Serializable |
Serializable[] |
short |
short[] |
SparseArray |
|
Binder |
|
Bundle |
|
ArrayList |
|
Size (only in API 21+) |
|
SizeF (only in API 21+) |
클래스가 위의 목록에 있는 클래스 중 하나를 확장하지 않는 경우
클래스를 parcelable로 만들려면 @Parcelize Kotlin 주석을 추가하거나
Parcelable을 직접 구현하면 됩니다.
비 parcelable 클래스 저장
클래스가 Parcelable 또는 Serializable을 구현하지 않으며 이러한 인터페이스 중 하나를 구현하기 위해 수정될 수도 없는 경우, 이 클래스의 인스턴스를 SavedStateHandle에 직접 저장할 수 없습니다.
Lifecycle 2.3.0-alpha03부터 SavedStateHandle는 Bundle로 객체를 저장하고 복원하는 자체 로직을 제공하여 모든 객체를 저장할 수 있습니다.setSavedStateProvider()
SavedStateRegistry.SavedStateProvider는 저장할 상태가 포함된 Bundle을 반환하는 단일 saveState() 메서드를 정의하는 인터페이스입니다. SavedStateHandle은 상태를 저장할 준비가 되면 saveState()를 호출하여 SavedStateProvider에서 Bundle을 검색하고 연결된 키용으로 Bundle을 저장합니다.
`ACTION_IMAGE_CAPTURE` 인텐트를 통해 카메라 앱에서 이미지를 요청하고 카메라가 이미지를 저장해야 하는 임시 파일에 전달하는 앱의 예를 생각해 보세요.ACTION_IMAGE_CAPTURE TempFileViewModel은 임시 파일을 만드는 로직을 캡슐화합니다.
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
활동의 프로세스가 중단되고 나중에 복원되는 경우 임시 파일이 손실되지 않도록 TempFileViewModel은 SavedStateHandle을 사용하여 데이터를 유지할 수 있습니다. TempFileViewModel이 데이터를 저장할 수 있게 하려면
SavedStateProvider을 구현하고 ViewModel의
SavedStateHandle에 관한 제공자로 설정하세요.
private fun File.saveTempFile() = bundleOf("path", absolutePath) class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
사용자가 돌아올 때 File 데이터를 복원하려면 SavedStateHandle에서 temp_file
Bundle을 검색합니다. 절대 경로가 포함된 saveTempFile()에서 제공하는 것과 동일한 Bundle입니다. 그런 다음 이 절대 경로를 사용하여 새 File을 인스턴스화할 수 있습니다.
private fun File.saveTempFile() = bundleOf("path", absolutePath) private fun Bundle.restoreTempFile() = if (containsKey("path")) { File(getString("path")) } else { null } class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { val tempFileBundle = savedStateHandle.get<Bundle>("temp_file") if (tempFileBundle != null) { tempFile = tempFileBundle.restoreTempFile() } savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
테스트의 SavedStateHandle
SavedStateHandle을 종속 항목으로 사용하는 ViewModel을 테스트하려면 필요한 테스트 값으로 SavedStateHandle의 새 인스턴스를 만들어 테스트 중인 ViewModel 인스턴스에 전달합니다.
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
추가 리소스
ViewModel의 저장된 상태 모듈에 관한 자세한 내용은 다음 리소스를 참고하세요.
Codelab
콘텐츠 보기
추천 서비스
- 참고: JavaScript가 사용 중지되어 있으면 링크 텍스트가 표시됩니다.
- UI 상태 저장
- 관찰 가능한 데이터 객체로 작업
- 종속 항목이 있는 ViewModel 만들기