안녕하세요. 점냥입니다.
이번 포스팅은 지난 UI 상태 저장 시리즈의 두 번째 글입니다.
1. Android에서 제공해주는 Ui View 상태 저장
2. ViewTree 내에 동일한 ID을 가진 위젯이 존재할 경우 UI 상태 저장 오류 해결 방법
Andorid 위젯은 기본적으로 ID가 지정되면 상태를 저장한다고 지난 시간에 이야기를 했습니다.
그런데 이로 인해 생각하지 못했던 부분에서 문제가 발생할 수도 있더라구요.. 실제로 얼마전 회사에서 몇시간동안 원인을 몰라 머리를 끙 싸맸던 문제였습니다. 제목처럼 동일한 2개 이상의 ViewGroup를 가진 ViewTree 내에 동일한 Id를 가진 위젯이 존재하는 경우입니다.
먼저 문제 상황을 볼까요?
문제 상황
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:importantForAutofill="no"
tools:ignore="LabelFor"
android:inputType="text" />
</FrameLayout>
위처럼 EditText를 FrameLayout인 ViewGroup으로 한번 감싼 커스텀 뷰가 있습니다. id는 edittext
로 지정되어 있네요.
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
...
tools:context=".MainActivity">
<TextView
...
android:text="첫번째 커스텀 뷰"
.../>
<com.post.duplicatedid.InsideEditTextView
android:id="@+id/customViewOne"
... />
<TextView
...
android:text="두번째 커스텀 뷰"
... />
<com.post.duplicatedid.InsideEditTextView
android:id="@+id/customViewTwo"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
activity xml에서는 위에서 정의한 내부에 EditText를 가지고 있는 InsideEditTextView를 2개 선언해주고 있네요.
자세히보면 각각 customViewOne, customViewTwo id를 지정한 것처럼 보이지만 사실 커스텀뷰의 id를 지정한 것이지 내부 EditText를 id를 지정한 것이 아니죠. 따라서 그림으로 간단히 표현하면 아래처럼 동일한 ID를 가지게 됩니다.
문제 해결 - Custom SaveState 등록하기
위 문제를 해결하는 방법은 아주 간단합니다! Android에서 자동으로 제공해주는 UI 상태저장을 사용하지 않고 Custom하게 구현하는 것입니다.
단계 1. Custom SavedState 만들기
class InsideEditTextViewState : BaseSavedState {
//EditText text 저장 변수
var value: String? = null
constructor(superState: Parcelable?) : super(superState) {}
constructor(source: Parcel) : super(source) {
value = source.readString()
}
override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)
dest.writeString(value)
}
companion object CREATOR : Parcelable.Creator<InsideEditTextViewState?> {
override fun createFromParcel(`in`: Parcel): InsideEditTextViewState {
return InsideEditTextViewState(`in`)
}
override fun newArray(size: Int): Array<InsideEditTextViewState?> {
return arrayOfNulls(size)
}
}
}
BaseSavedState는 View에 inner class로 선언되어 있으며 최종적으로 Parcelable를 상속받는 클래스입니다.
커스텀하게 만든 SavedState 객체에 저장, 복원하고 싶은 값을 선언해주면 됩니다.
저의 경우에는 EditText의 입력한 텍스트이기 때문에 String 자료형의 value 변수를 선언해주었습니다.
단계 2. View 생명 주기에 Custom SaveState 반영하기
View의 Lifecycle 중 이렇게 상태저장을 위한 2가지 콜백 함수가 있습니다.
onSaveInstanceState()
- UI 상태를 저장하고자 할 때 시스템으로부터 호출되는 콜백 메소드
onRestoreInstanceState(Parcelable state)
- UI 상태를 복원하고자 할 때 시스템으로부터 호출되는 콜백 메소드
override fun onSaveInstanceState(): Parcelable {
return super.onSaveInstanceState().run {
InsideEditTextViewState(this).apply {
value = binding.editText.text.toString()
}
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
val insideEditTextViewState = state as? InsideEditTextViewState ?: return
super.onRestoreInstanceState(insideEditTextViewState.superState)
binding.editText.setText(insideEditTextViewState.value)
}
단계 3. System UI 상태저장 기능 끄기
지난 시리즈에서 UI 상태저장 기능을 자동으로 사용하기 위해서는 뷰의 Id 지정하는 것과 SaveEnabled 속성을 true로 설정하는 것이었습니다. (기본값이 true입니다)
현재 문제는 시스템에서 제공해주는 상태저장이 아닌 저희가 정의한 InsideEditTextViewState를 사용해야합니다.
따라서 상태 저장을 꺼야하는 데 Id는 사실 부여하지 않으면 xml 정의한 뷰에 접근하는 것이 불가능하기 때문에 SaveEnabled을 false로 수정해주어야 합니다.
<EditText
android:id="@+id/editText"
...
android:saveEnabled="false"
.../>
'Android > Common' 카테고리의 다른 글
[Android] EventBus (0) | 2022.08.28 |
---|---|
[Android] Bottom Navigation Bar State with Compose - (2) (0) | 2022.06.12 |
[Android] Android에서 제공해주는 UI View 상태 저장 - 1 (0) | 2022.05.01 |
[Android] Paging3 + Admob Native Ad (0) | 2021.11.01 |
[Android] 좋아요 기능으로 알아보는 더블 클릭 방지하는 방법 (0) | 2021.09.04 |