앱에서 UI 상태를 어떻게 잃을 수 있습니까?
Handle configuration changes : https://goo.gle/configuration-changes
Activity Recreate를 완전히 피하는 것은 불가능합니다. Configuration 변경에도 데이터가 유지되도록 하려면 ViewModel API를 사용해야 합니다.
ViewModel의 목적은 2가지가 있습니다.
ViewModel API는 Activity, Fragment, Navigation destination 범위에서 Configuration 변경 후에도 데이터를 유지할 수 있는 유일한 방법입니다.
예상치 못한 앱 종료 후에도 데이터가 살아남으려면 메모리 대신 디스크에 정보를 저장해야 합니다. 네트워크를 통해 자체 데이터 서버에 저장하는 것도 방법 중 하나입니다.
로컬 디바이스에 데이터를 저장하는 방법으로 Jetpack의 DataStore와 Room이 있습니다.
이 경우는 시스템에 의해 프로세스가 중단될 수 있습니다. 앱으로 다시 돌아갈 때 프로세스를 다시 생성합니다. 디스크에 저장하는 앱 데이터는 문제없지만, UI 상태는 메모리에 있으므로 모든 것을 잃게 됩니다.
SavedState API를 통해서 필수 데이터를 저장하는 방법을 제공하여 프로세스가 다시 생성되기 전의 상태로 돌아갈 수 있도록 제공할 수 있습니다. SavedState API는 내부적으로 Android Bundle 객체에 의존합니다.
상태를 저장하는 API로는
View 시스템에서 동일하게 상태를 저장하려면 onSaveInstanceState/onRestoreInstanceState 함수를 사용해야 합니다. 주의할 점은 View에 isSaveEnabled = true
이여야 하며 View에 ID가 있어야 한다는 것입니다.
Saved State API를 테스트하려면 아래 API를 사용하면 됩니다.
Compose에서 상태 복원 테스트 방법과 동작 테스트 샘플
ViewModel을 State Holder로 사용하는 경우에는 SavedStateHandle
API를 사용해야 합니다.
SavedStateHandle가 유효한 경우는 Activity가 Stop될 때 쓰여진 데이터만 저장한다는 것입니다.
Saved State module for ViewModel : https://goo.gle/architecture-viewmodel-savedstate
ViewModel은 재사용할 수 있는 UI 요소의 복잡성을 관리하기에는 좋은 솔루션은 아닙니다.
다음 예는 뉴스에서 재사용할 수 있는 검색 UI 요소가 있고, 검색된 사용자 입력을 Saved State로 처리하고 싶은 경우입니다.
Compose에서 상태를 Saved State로 대응하는 방법은 rememberSaveable API를 사용방법이 있습니다. Compose API 스펙대로 rememberSaveable을 내부적으로 사용하는 remember 함수로 만들 수 있습니다.
NewsSearchState는 복잡한 객체이므로 Custom Saver를 제공해야 합니다. Saver는 객체를 저장 가능한 상태로 변환하여 저장할 수 있도록 합니다.
Saver는 save/restore 두 가지 함수를 제공해야 합니다. TextFieldValue에는 자체 Saver가 있으므로 해당 기능을 위임하고 현재 검색 입력을 저장/복원하면 됩니다. 복원된 검색 입력과 Saver 함수에 전달할 NewsRepository를 전달하여 NewsSearchState의 새 인스턴스를 생성합니다.
최종적으로 rememberNewsSearchState에 saver를 호출하여 처리할 수 있습니다.
NewsSearchState라는 Holder Class는 몇 가지의 제약이 있습니다.
SavedStateRegistry는 컴포넌트가 저장된 인스턴스 상태 메커니즘을 사용하여 상태를 저장/복원할 수 있는 인터페이스입니다. RegistryOwner의 saveState에 콘텐츠를 제공할 수 있는 Provider도 있습니다.
현재 검색어를 saveState에 저장하려면 SavedStateProvider 인터페이스를 구현하고, SavedStateRegistryOwner가 중지되기 전에 호출되는 saveState 메소드를 구현해야 합니다.
클래스 초기화 시 검색 상태를 SavedStateRegistryOwner에 Provider로 등록합니다. 그다음으로 이전에 저장된 상태라면 consumeRestoredStateForKey 메소드를 호출하여 상태를 복원할 수 있습니다.
View에서 검색 UI 정보를 사용하는 경우 State Holder를 초기화할 수 있습니다.
기본적으로 save 이벤트가 발생하기 전에 UI 요소가 composition에 있었다면 rememberSaveable의 값이 복원됩니다.
Composable은 Composable에 들어온 후 0번 이상 recomposition이 일어날 수 있고, 최종적으로 Composable에서 떠납니다.
UI가 Composable에 진입할 때 rememberSaveable의 값이 Saved State에 저장되며 Configuration 변경으로 Activity가 재생성되면 이전 Composition이 파괴되고 새로운 Composition 생성되며 rememberSaveable의 값이 복원됩니다.
Composable에서 벗어나면 rememberSaveable 내부의 값은 Saved State에서 제거됩니다.
- rememberSaveable API : Activity 재생성시 값은 복원됨
- remember API : Activity 재생성시 값은 손실됨
Compose에도 View 시스템과 동일하게 상태를 저장/복원할 수 있는 SaveableStateRegistry 인터페이스가 있습니다. 이 인터페이스가 Android 플랫폼에 구애받지 않는다는 것이 다른 부분입니다.
Compose가 Android 타겟으로 실행되면 DisposableSaveableStateRegistry 구현을 통해 SaveableStateRegistry와 연결됩니다.
SaveableStateProvider를 사용하여 SavedState 값을 제어할 수 있는 SaveableStateHolder가 있습니다.
Compose서 rememberSaveableStateHolder 함수를 사용해 API의 인스턴스를 생성할 수 있습니다. 컴포넌트의 특정 부분에서 rememberSaveable 값의 수명 주기를 제어하고 싶을 때 필요합니다.
RememberSaveable : https://github.com/androidx/androidx/blob/androidx-main/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/RememberSaveable.kt
rememberSaveable 구현체에는 LocalSaveableStateRegistry에 접근하여 consumeRestored를 호출하여 초기화합니다. 이전에 저장된 값이 없는 경우, 전달된 init 함수를 통해 초기화됩니다.
이 방법이 Jetpack Navigation Compose가 ViewModel 인스턴스 캐싱, rememberSaveable 값을 메모리에 유지하는데도 사용합니다.
특정 대상의 content가 backStackEntry에서 호출되는 LocalOwnersProvider 내부에 배치됩니다.
LocalOwnersProvider는 몇 가지의 LocalComposition을 가지고 있으며 사용자 정의 SaveableStateProvider를 호출합니다. 이 SaveableStateProvider로 rememberSaveable 값을 Registry에서의 생명주기를 제어합니다.
이 예에서는 SaveableStateHolder와 연결되어 각각 다른 ID를 가지는 destination이 존재합니다. 새로운 화면이 열리면, 새로운 destination가 BackStack에 추가됩니다.
이전 화면으로 돌아가면 destination이 BackStack에서 제거됩니다. 그리고 Navigation은 해당 ID로 removeState 함수를 호출하여 관련된 모든 rememberSaveableState를 제거합니다.
변경빈도가 높고 지연이 거의 없음 : ViewModel > Saved State APIs > Persistent Storage
데이터 손실이 없지만, 느리고 안정적임 : Persistent Storage > Saved State APIs > ViewModel
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024