앱을 만들 때 landscape 모드를 고려하기 위해서 몇가지 사항이 있다.
가장 크게 고려해야할 사항은 데이터의 보존이다.
만약 세로모드에서 작업한 내용들이 화면을 회전함에 따라 값이 초기화된다면 이는 큰 오류이다.
이것을 보존하기 위해 ViewModel Scope에 대해 알아볼 것이다.
Activity 생명주기
안드로이드 생명주기는 위 그림과 같다.
하지만 여기에 큰 문제가 존재한다.
만약 화면을 가로모드로 바꾸거나 세로모드로 바꾸게 된다면 즉, 화면을 회전하게 되면 onDestory() 가 실행되고 다시 onCreate()가 호출된다.
이것은 화면 회전 시 작업하던 모든 데이터가 초기화 되는 것을 의미한다.
이것을 대비하기 위해서 몇 가지 방법이 있다.
1. onSaveInstanceState를 이용하여 상태를 보존하는 방법
2. ViewModel 을 이용하는 방법이다.
우선 ViewModel을 먼저 알아보자.
ViewModel
ViewModel 클래스 는 액티비티와 프래그먼트에서 사용되는 UI 관련 데이터를 보관하고, 관리하기 위해 사용된다. 그리고 액티비티와 애플리케이션 내 다른 클래스들과 커뮤니케이션하는데 사용된다.
ViewModel 클래스는 액티비티나 프래그먼트의 Scope와 함께 생성되고, 이 Scope가 살아있는 동안에 유지된다. 다르게 말하면, 액티비티나 프래그먼트가 Screen Rotation 등의 이유로 Destroy 상태가 되더라도 ViewModel은 Destroy 되지 않는 것을 의미한다.
예제
Activity lifeCycle 을 이용한 예제이다.
Button, TextView 를 만들고 버튼을 누르면 카운터가 1만큼 증가한 값을 표시하는 예제이다.
class MainActivity : AppCompatActivity() {
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
var counter = 100
binding.textVew.text = counter.toString()
binding.button.setOnClickListener {
counter++
binding.textVew.text = counter.toString()
}
}
}
이것을 실행시키고 화면을 회전하면 값이 유지될까?
위에서도 설명했지만, 화면을 회전하면서 onDestroy가 실행되고 다시 onCreate가 실행되어 counter의 값이 100으로 다시 초기화 되었기에 변경했던 값이 유지되지 않는다.
이제 ViewModel 을 이용하여 변경된 값을 유지시켜 보자.
ViewModel
class MyViewModel : ViewModel() {
private var _count = MutableLiveData<Int>()
val count: LiveData<Int>
get() = _count
init {
_count.value = 100
}
fun increaseValue() {
_count.value = _count.value!! + 1
}
}
위는 ViewModel 코드이다.
LiveData를 통해 값을 초기화 시켜주고 increaseValue 함수를 통해 값을 증가 시킨다.
class MainActivity : AppCompatActivity() {
private val binding: ActivityMainBinding by lazy {
ActivityMainBinding.inflate(layoutInflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
val myViewModel: MyViewModel by viewModels()
binding.textVew.text = myViewModel.count.value.toString()
myViewModel.count.observe(this, Observer {
binding.textVew.text = myViewModel.count.value.toString()
})
binding.button.setOnClickListener {
myViewModel.increaseValue()
}
}
}
Activity에서는 ViewModel을 생성하여 Observing 하여 값을 감시하고, 값이 변경된다면 텍스트 뷰를 변경시키는 코드를 작성했다.
이것을 실행시키면 아래와 같이 작동한다.
ViewModel을 사용했기에 Screen Rotation 이슈에도 변경된 값이 그대로 살아있는 모습이다.
문제는 마지막에 보이다시피 잠깐 앱을 백그라운드에 보냈다가 다시 실행시키면 변경된 값이 삭제된다.
앱이 비정상적으로 종료되었을 때에도 값을 유지하고 싶다면 onSaveInstanceState를 사용하면 된다.
onSaveInstanceState
액티비티 코드는 그대로 유지한 채 ViewModel 코드만 수정하면 된다.
class MyViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
private var _count = MutableLiveData<Int>()
val count: LiveData<Int>
get() = _count
init {
_count.value = savedStateHandle.get<Int>("counter") ?: 0
}
fun increaseValue(){
_count.value = _count.value!! + 1
savedStateHandle["counter"] = _count.value
}
}
ViewModel 파라미터에 SavedStateHandel을 추가하였다.
SavedStateHandel은 값을 키 - 값 구조로 저장한다.
"counter" 키에 _count.value 의 값을 저장하고, 저장된 값을 가져온다면 앱이 비정상적으로 종료되어도 변경된 값을 유지할 수 있다.
구글에서는 Activity를 재 생성할 때 사라지는 휘발성 Data를 잠깐 보관하기 위해 onSaveInstanceState를 만들어 제공했다.
이를 사용하면 Map 형식으로 Bundle에 데이터를 저장하고 가져올 수 있다.
ViewModel과 Save Instance State 를 비교한 표이다.
둘의 가장 핵심적인 차이는 onFinish 이후에도 사용 가능 여부이다.
onSaveInstanceState는 사용자의 완전한 활동 종료에도 값이 유지되는 장점이 있지만, 읽기/쓰기 시간이 느리다.
또한 구글이 Data의 크기를 50k로 제한하도록 권장하기에 거대한 Data를 다루기 위해 만들어진 포맷이 아니다.
'안찌의 개발일기 > Android' 카테고리의 다른 글
[Android] Compose Preview 심폐소생술!! (1) | 2024.11.19 |
---|---|
[Android] 서버 통신 (2) | 2024.09.25 |
[Android] 코루틴 (Coroutine) (1) | 2024.06.18 |
[Android] RecyclerView, DiffUtil (1) | 2024.06.13 |
[Android] 뷰 바인딩 (View Binding) (1) | 2024.05.24 |