아래 표를 보면 ViewModel을 사용하는 것만으로도 데이터의 손질을 방지하고 유지할 수 있다. 하지만 ViewModel의 경우 프로세스 중단 시에 유지가 되지 않으므로 시스템에서 시작된 프로세스를 중단 한 경우에도 UI상태를 유지하고자 한다면
저장된 인스턴스 상태 (Saved Instance State)
를 사용해야 한다.
ViewModel | 저장된 인스턴스 상태 | 영구 저장소 | |
---|---|---|---|
저장소 위치 | 메모리 내 | 디스크에 직렬화 | 디스크 또는 네트워크 내 |
구성 변경 시에도 유지 | 예 | 예 | 예 |
시스템에서 시작된 프로세스 중단 시에도 유지 | 아니요 | 예 | 예 |
사용자의 완전한 활동 닫기 /onFinish() 시에도 유지 | 아니요 | 아니요 | 예 |
데이터 제한 | 복잡한 객체도 괜찮지만 사용 가능한 메모리에 의해 공간이 제한 됨 | 원시 유형 및 문자열과 같은 단순하고 작은 객체만 해당 | 디스크 공간 또는 네트워크 리소스에서 검색하는 비용/시간에 의해서만 제한됨 |
읽기/쓰기 시간 | 빠름(메모리 액세스만) | 느림(직렬화/역직렬화 및 디스크 액세스 필요) | 느림(디스크 액세스 또는 네트워크 트랜잭션 필요) |
onSaveInstanceState()
및 rememberSaveable
API와 SavedStateHandle
을 포함한다. SavedStateHandle
: 비즈니스 로직에 사용되는 상태의 경우, ViewModel에 유지하고 SavedStateHandle을 사용하여 저장한다.onSaveInstanceState
, rememberSaveable
: UI 로직에 사용되는 상태의 경우 class SignUpViewModel(private val handle: SavedStateHandle) : ViewModel() {
...
}
Application, SavedStateHandle
또는 SavedStateHandle
이라면 이미 정의된 SavedStateViewModelFactory
클래스를 이용하여 AbstractSavedStateViewModelFactory를 대신할 수 있다. class SignUpActivity : AppCompatActivity() {
companion object {
const val EXTRA_ENTRY_TYPE = "extra_entry_type"
const val EXTRA_USER_TYPE = "extra_user_entity"
fun newIntent(
context: Context,
entryType: SignUpEntryType,
entity: SignUpUserEntity? = null
): Intent =
Intent(
context,
SignUpActivity::class.java
).apply {
putExtra(EXTRA_ENTRY_TYPE, entryType.ordinal)
putExtra(EXTRA_USER_TYPE, entity)
}
}
// SavedStateViewModelFactory를 이용한 ViewModel 인스턴스화
private val signUpViewModel: SignUpViewModel by viewModels {
SavedStateViewModelFactory(application, this, intent?.extras)
}
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
SavedStateHandle
객체는 저장된 상태에서 객체를 작성하고 저장된 상태에서 객체를 검색할 수 있게 하는 키-값 맵
이다. 이러한 값은 시스템에서 프로세스가 중단된 후에도 유지되며 동일한 객체를 계속 사용할 수 있다.
키-값 맵
에 필요한 메서드
Intent로 넘어온 데이터를 출력해보면 다음과 같이 key와 key에 해당하는 값을 확인할 수 있다.
class SignUpViewModel(private val handle: SavedStateHandle) : ViewModel() {
private val _entryType: MutableLiveData<SignUpEntryType> = MutableLiveData()
val entryType: LiveData<SignUpEntryType> get() = _entryType
private val _userEntity: MutableLiveData<SignUpUserEntity> = MutableLiveData()
val userEntity: LiveData<SignUpUserEntity> get() = _userEntity
init {
// Intent로 전달받은 데이터 확인
handle.keys().forEach { key ->
Log.d("SignUpViewModel", "Received [$key]=[${handle.get<Any>(key)}]")
}
}
}
Received [extra_user_entity]=[SignUpUserEntity(name=스파르타, email=hello, emailService=r.com)]
Received [extra_entry_type]=[1]
class SignUpViewModel(private val handle: SavedStateHandle) : ViewModel() {
private val _entryType: MutableLiveData<SignUpEntryType> = MutableLiveData()
val entryType: LiveData<SignUpEntryType> get() = _entryType
private val _userEntity: MutableLiveData<SignUpUserEntity> = MutableLiveData()
val userEntity: LiveData<SignUpUserEntity> get() = _userEntity
...
init {
// SavedStateHandle 객체에서 SignUpEntryType형의 EXTRA_ENTRY_TYPE 키를 가진 객체 받아오기
val entryTypeOrdinal = handle[EXTRA_ENTRY_TYPE] ?: SignUpEntryType.CREATE.ordinal
setEntryType(SignUpEntryType.values()[entryTypeOrdinal])
// SavedStateHandle 객체에서 SignUpEntryType형의 EXTRA_USER_TYPE 키를 가진 객체 받아오기
if (entryType.value == SignUpEntryType.UPDATE) {
setUserEntity(handle[EXTRA_USER_TYPE] ?: SignUpUserEntity("", "", ""))
}
}
private fun setEntryType(entryType: SignUpEntryType) {
// savedStateHandle에 저장하기
handle[EXTRA_ENTRY_TYPE] = entryType.ordinal
_entryType.value = entryType
}
private fun setUserEntity(entity: SignUpUserEntity) {
// savedStateHandle에 저장하기
handle[EXTRA_USER_TYPE] = entity
_userEntity.value = entity
}
}
참고
SavedStateHandle과 함께 ViewModel의 상태를 저장하자
[Jetpack] ViewModel의 SavedStateHandle