[Jetpack] ViewModel의 SavedStateHandle

이동건·2023년 4월 23일
5

jetpack

목록 보기
2/3

사용자는 애플리케이션을 나갔다오거나 구성을 변경하더라도, UI에서 보여지는 상태가 보존되길 기대한다. 하지만 Activity 또는 프로세스 재생성으로 인해 UI 상태가 손실 될 수 있다. 이는 다음과 같은 이벤트들로 발생할 수 있다.

  • 구성 변경 : 화면 방향, 글꼴 크기 및 두께, 언어, 다크모드 여부 조정, 멀티 윈도우 모드
  • 시스템에서 시작된 프로세스 종료 : 앱이 백그라운드에 있고 기기가 리소스(메모리)를 다른 프로세스에서 사용할 수 있도록 확보

ViewModel을 사용하면 구성 변경이 일어나도 UI 상태를 유지하는 것은 기존에 알고 있었다. 하지만 시스템에서 프로세스를 종료하는 경우에 대해서는 잘 몰랐다. 이런 상황이 언제 일어나는지 알아보고, 이를 대응하는 방법에 대해 공식문서를 정독하고 정리해 보았다.


UI 상태 닫기

https://developer.android.com/topic/libraries/architecture/saving-states?hl=ko

사용자시스템에 의해 UI 상태가 닫히고 손실되는 시나리오에 대해 각각 알아보자.

사용자가 시작한 UI 상태 닫기

사용자는 화면(액티비티)을 시작할 때, 해당 화면을 완전히 닫을 때까지 일시적인 UI 상태가 유지될 것으로 기대한다. 사용자는 다음 동작으로 액티비티를 완전히 닫을 수 있다.

  • 최근 사용 화면에서 스와이프 하여 닫기
  • 설정 화면에서 앱 종료 또는 강제 종료
  • 기기 재부팅
  • Activity.finish()가 이루어지는 일종의 마무리 작업 진행

이러한 상황에서 사용자는 해당 화면(액티비티)를 완전히 벗어났다고 생각하며, 다시 열 경우 새로 시작될 것을 기대한다. 이는 기본적인 시스템 동작과 일치한다. 액티비티 객체는 내부에 저장된 상태와 액티비티와 관련된 저장된 InstanceState 기록과 함께 메모리에서 폐기되고 삭제된다.

하지만 사용자의 기대에도 예외가 있다. 예를 들어 사용자가 브라우저를 강제 종료하고 다시 앱을 켠 경우, 사용자는 보고 있었던 웹 페이지로 되돌아갈 것으로 기대한다.

시스템이 시작된 UI 상태 닫기

사용자는 구성 변경이 일어나도 UI 상태가 동일하게 유지될 것으로 기대한다. 하지만 시스템은 기본적으로 이러한 변경이 발생할 때, 액티비티를 폐기하고 저장된 UI 상태를 완전히 삭제한다. 물론 구성 변경에 대한 동작을 재정의할 순 있찌만, 이는 구글에서 권장하지 않는다.

또한 사용자는 일시적으로 다른 앱으로 전환한 후 나중에 다시 사용하던 앱으로 돌아오면 UI 상태가 동일하게 유지될 것으로 기대한다. 이 시나리오에서 앱은 백그라운드에 배치된다. 하지만 사용자가 다른 앱과 상호작용 하는 동안, 시스템은 메모리 확보를 위해 애플리케이션 프로세스를 파괴할 수 있다. 사용자가 다시 앱을 키면 예상과 달리 깨끗한 상태이다.

프로세스 및 애플리케이션 생명주기는 해당 사이트를 참조하자. https://developer.android.com/guide/components/activities/process-lifecycle?hl=ko


UI 상태를 유지하기 위한 옵션

UI 상태에 관한 사용자의 기대치가 기본 시스템 동작과 일치하지 않으면 사용자의 UI 상태를 저장하고 복원하여 시스템에 의한 폐기를 사용자가 인식할 수 있도록 해야한다.

UI 상태를 유지하기 위해 선택할 수 있는 옵션들은 다음과 같다.

구성변경에 있어서는, ViewModel을 사용하는 것만으로도 UI상태를 유지할 수 있다. 하지만 시스템에서 프로세스를 종료한 경우에도 유지하고자 한다면, 저장된 인스턴스 상태(Saved Instance State) 사용을 고려해야 한다.

위 표의 저장된 인스턴스 상태는 ViewModel의 일부로 onSaveInstanceState()및 rememberSaveableAPI와 SavedStateHandle을 포함한다.

저장된 인스턴스 상태

Activity의 onSaveInstanceState()콜백, Jetpack Compose의 rememberSaveable , ViewModel의 SavedStateHandle 은 시스템이 활동 또는 프래그먼트와 같은 UI 컨트롤러를 폐기하고 나중에 다시 생성할 때 컨트롤러의 상태를 다시 로드하는 데 필요한 데이터를 저장한다.

하지만 여러 API가 데이터를 직렬화하기 때문에 저장용량 및 속도의 제한이 있다. 직렬화될 객체가 복잡하면 직렬화 시 많은 메모리가 소비될 수 있으며, 직렬화 프로세스는 기본 스레드에서 발생하기 때문에 직렬화가 장기적으로 실행되면 프레임 하락 및 시각적인 끊김 현상이 발생할 수 있다.

물론 앱에 따라 이러한 저장된 인스턴스 상태를 전혀 사용할 필요가 없을 수도 있다!! 일반적으로 저장된 인스턴스 상태에 저장하는 데이터는 사용자 입력 또는 탐색에 따라 달라지는 임시 상태이다.

  • 목록 스크롤 위치
  • 사용자가 더 자세히 알고자 하는 항목의 ID
  • 진행 중인 사용자 환결설정 선택
  • 텍스트 필드에 입력

SavedStateHandle

저장된 인스턴스를 어떠한 API로 사용할지는 상태가 유지된 위치와 필요한 로직에 따라 다르다. 비즈니스 로직에 사용되는 상태의 경우, ViewModel에 유지하고 SavedStateHandle을 사용하여 저장한다. UI 로직에 사용되는 상태의 경우, onSaveInstanceState API 또는 Compose의 rememberSaveable을 사용한다. 나는 주로 뷰모델에 상태를 저장하고 뷰는 이를 참조하는 방식으로 구현하므로, SavedStateHandle을 사용해서 구현해보고자 한다.

ViewModel에서 SavedStateHandle을 사용하려면, 다음과 같이 SavedStateHandle을 받는 생성자를 포함시켜야 한다.

프로젝트에서 Hilt를 사용하고 있다면 ViewModelFactory를 따로 구현하지 않고 단순히 생성자에 주입만 하면 된다. (Hilt 만세!)

SavedStateHandle 객체는 저장된 상태에 객체를 작성하고 저장된 상태에서 객체를 검색할 수 있게 하는 키-값 맵이다. 이러한 값은 시스템에서 프로세스가 중단된 후에도 유지되며 동일한 객체를 계속 사용할 수 있다. 이렇게 저장된 상태는 앱을 강제종료하거나 최근 메뉴에서 제거하거나 기기를 재부팅 할때 사라지며 복원이 불가능하다. 즉, 사용자가 시작한 UI 상태 닫기 시나리오에선 저장된 상태가 복원되지 않고, 시스템에서 시작된 시나리오에서는 복원이 된다.

지원되는 유형

기본적으로 Bundle과 동일한 데이터 유형에 대해 set과 get 메서드 호출이 가능하다.

Parcelable이 아닌 클래스를 저장하는 방법에 대해서는 공식문서를 참고하자.

사용 예

기존에 닉네임 변경화면에서 savedStateHandle을 사용하지 않으면, 다음과 같이 액티비티가 종료된 경우 TextField의 텍스트 값이 유지되지 않는다.

하지만 ViewModel에서 savedStateHandle을 사용하면, 해당 상태를 저장하고 복원할 수 있다! 코드는 다음과 같다.

@HiltViewModel
class NicknameViewModel @Inject constructor(
	private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
	val originNickname = "닉변테스트"
  val nickname = mutableStateOf(originNickname)
	init {
		// savedStateHandle에서 받아오기
		savedStateHandle.get<String>("editedName")?.let {
            changeNickname(it)
    }
	}
	fun changeNickname(editedName: String){
		 // savedStateHandle에 저장하기
     savedStateHandle["editedName"] = editedName
     nickname.value = editedName
  }
}

시스템에 의해 액티비티가 종료되는 것을 테스트하기 위해, 개발자모드 - 활동 유지 안함을 설정하였다.


참고

https://developer.android.com/topic/libraries/architecture/saving-states?hl=ko

https://www.charlezz.com/?p=44175

profile
성장하는 활동적인 개발자

0개의 댓글