SavedStateHandle과 함께 ViewModel의 상태를 저장하자

UI 상태 저장 및 복원의 필요성

안드로이드의 Activity는 시스템의 요청에 의해 언제든지 소멸될 수 있다. 사용자는 UI상태가 동일하게 유지되길 기대하기 때문에 Activity의 상태를 적시에 보존하고 복원하는 작업이 필요하다.

화면 회전 또는 멀티 윈도우 모드로 전환하는 것과 같이 Configuration이 변경되어도 사용자는 Activity의 UI상태가 그대로 유지하기를 기대한다. Activity는 Configuration이 변경되면 기존 Activity를 소멸시키고 새로운 Activity 인스턴스를 생성하기 때문에 이전 UI상태가 모두 날아가버린다.

이를 방지하기 위해서는 Activity가 가지고 있는 내부 콜백 메서드인 onSavedInstanceState() 와 onRestoreInstanceState() 를 통해 상태를 저장하고 복구할 수 있다. 그러나 Configuration변경의 경우 ViewModel을 사용하는 것 만으로도 UI상태를 유지할 수도 있다. 대부분의 경우에는 ViewModel과 저장된 인스턴스 상태(savedInstanceState)를 모두 사용해야한다.

아래 영상을 통 UI 상태를 유지하지 못하는 앱을 확인하자.

https://www.charlezz.com/wordpress/wp-content/uploads/2020/06/ViewModel-Basic.mp4

https://www.charlezz.com/wordpress/wp-content/uploads/2020/06/ViewModel-Basic

Activity의 종료 그리고 상태 저장 및 복원 시기

사용자가 명시적으로 Activity를 종료하는 경우는 UI상태를 저장할 필요가 없기 때문에 시스템에서는 Activity와 관련된 ViewModel 및 저장된 인스턴스 상태를 메모리에서 삭제시킨다.

사용자가 명시적으로 Activity를 종료한 케이스는 다음과 같다.

  • Back 버튼 누르기

사용자가 Activity를 명시적으로 종료하지 않았는데 Activity가 종료되는 케이스는 Configuration변경과 시스템에 의한 종료가 있다.

Configuration 변경은 런타임에 발생할 수 있는 Activity의 회전, 멀티 윈도우, 키보드 가용성 등을 말하며 이 경우 Activity를 다시 시작하게 된다. Configuration 변경의 경우 ViewModel을 사용하는 것만으로도 UI상태를 보존할 수 있다. 하지만 시스템에 의해서 Activity가 종료되는 경우 ViewModel도 같이 메모리에서 제거 되기 때문에 UI 상태를 보존할 수 없다.

시스템은 RAM에 여유 공간이 필요할 때 프로세스를 종료시킨다. 시스템이 특정 프로세스를 종료할 가능성은 그 시점의 프로세스 상태에 따라 달라진다. 그리고 프로세스 상태는 프로세스에서 실행되는 Activity 상태에 따라 달라진다. 다음은 프로세스 상태, Activity상태, 시스템이 프로세스를 종료할 가능성 사이의 상관관계를 나타낸다.

https://developer.android.com/guide/components/activities/activity-lifecycle.html?hl=ko#asem[/caption]

시스템에 의해서 Activity가 종료되는 경우 저장된 인스턴스 상태 또는 데이터베이스에 UI상태를 저장하여 복원하는 방법이 있다. 다음의 표는 UI 상태를 유지하기 위해 고려할만한 사항들을 정리하고 있다.

SavedStateHandle을 이용한 상태 저장/복원 구현하기

ViewModel을 위한 saved state 모듈은 lifecycle 버전 2.2.0에서 추가되었다.

build.gradle에 다음과 같은 아티팩트의 종속성을 추가하자. (최신 버전 확인)

dependencies {
def lifecycle_version = "2.2.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
}

SavedStateHandle을 ViewModel에서 다루기 위해서는 다음과 같이 SavedStateHandle을 받는 생성자를 ViewModel에 포함해야 한다.

class MainViewModel(val savedStateHandle: SavedStateHandle) : ViewModel() {
...
}

SavedStateHandle을 받도록 ViewModel 인스턴스를 생성하려면 AbstractSavedStateViewModelFactory를 확장하는 Factory 클래스를 만들어야 한다.

만약 ViewModel 생성자 파라미터 시그니쳐가 (Application, SavedStateHandle) 또는 (SavedStateHandle) 이라면 이미 정의된 SavedStateViewModelFactory 클래스를 이용하여 AbstractSavedStateViewModelFactory 구현을 대신할 수도 있다.

SavedStateViewModelFactory를 이용한 ViewModel 인스턴스화를 하는 코드는 다음과 같을 수 있다.

class MainActivity : AppCompatActivity() {    private lateinit var viewModel: MainViewModel    override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ViewModelProvider(this, SavedStateViewModelFactory(application, this)).get(MainViewModel::class.java)
}
}

이제 ViewModel의 SavedStateHandle을 이용하여 간단한 데이터를 저장하고 복원하는 코드를 구현할 수 있다. 다음은 정수형 count 변수의 상태를 저장하고 복원하는 코드다.

class MainViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {    var count = 0
set(value) {
savedStateHandle.set("count",value)
field = value
}
init {
savedStateHandle.get<Int>("count")?.run {
count = this
}
}
}

키 — 값 형식으로 저장이 가능하고 불러오는 것이 가능하다.

SavedStateHandle 클래스에는 다음과 같이 키-값 맵에 필요한 메서드가 있다.

  • get(String key)

또한 식별 가능한 LiveData 항목으로 래핑된 값을 반환하는 특별한 메서드 getLiveData(String key)도 있다.

SavedStateHandle로 부터 상태가 복원된 ViewModel을 참조하는 Activity는 다음과 같이 상태가 보존된 것을 확인할 수 있다.

[video width=”360" height=”736" mp4=”https://www.charlezz.com/wordpress/wp-content/uploads/2020/06/ViewModel-with-SavedStateHandle.mp4"][/video]

예제코드는 github에서 확인할 수 있다.

유의사항

  • SavedStateHandle에 저장되는 데이터는 단순하고 가벼워야 한다. 복잡하거나 큰 데이터의 경우 데이터베이스를 사용하도록 하자.

A passionate developer who’s curious about Android

A passionate developer who’s curious about Android