사건의 발단 🕵️
백그라운드 스레드에서 Room DB를 통해 Reservation(예약 정보)이라는 데이터를 받아오고, 해당 데이터를 UI에 렌더링하기 위해 데이터 바인딩을 사용했다. 이 과정에서 BindingAdapter를 활용하여 UI를 업데이트했다.
그러나 10번에 3번 꼴로 데이터 바인딩 프로퍼티가 null인 경우가 발생하여 앱이 크래시되는 문제가 있었다. 이러한 문제는 바인딩 프로퍼티가 누락되어 발생한 것으로 보인다. (아마 7번은 운 좋게 데이터 바인딩이 성공된 것으로 보인다)
코드는 다음과 같다.
// presenter 코드
override fun loadReservation(id: Long) {
thread {
reservationRepository.findReservation(id).onSuccess { reservation ->
showReservation(reservation, "선릉 극장")
}.onFailure { e ->
when (e) {
is NoSuchElementException -> {
view.showToastMessage(e)
view.navigateBackToPrevious()
}
else -> {
view.showToastMessage(e)
view.navigateBackToPrevious()
}
}
}
}
}
// view(Activity) 코드
override fun showReservation(
reservation: Reservation,
theaterName: String,
) {
runOnUiThread {
binding.reservation = reservation
binding.theaterName = theaterName
}
}
위 코드는 Presenter에서 백그라운드 스레드를 열어 Room DB에서 데이터를 가져온 후, 해당 데이터를 View에 전달하여 메인 스레드에서 데이터 바인딩하는 과정을 보여준다.
@BindingAdapter("count", "seats", "theaterName")
fun TextView.showReservation(
count: Int,
seats: List<Seat>,
theaterName: String,
) {
this.text =
this.context.getString(R.string.reserve_count, count, seats.toSeatString(), theaterName)
}
데이터 바인딩이 정상적으로 이루어졌다면 XML에서 BindingAdapter를 통해 뷰를 렌더링하게 된다.
범인 찾기 👀
범인을 찾기 위해 검증해야 할 두 가지가 있었다.
- 운이 좋게 백그라운드 스레드에서 가져온 데이터가 메인 스레드로 전달되어 뷰가 렌더링되었는가?
- BindingAdapter의 파라미터를 nullable하게 선언하여 null 값이 바인딩 되면서 앱이 크래시 나지 않는가?
나는 이 두 가지 검증을 통해 문제를 해결하려고 노력했다.
운이 좋게 백그라운드 스레드에서 가져온 데이터가 메인 스레드로 전달되어 뷰가 렌더링되었는가? 🧐
첫 번째 검증에서는 다양한 코드를 구현하면서 백그라운드 스레드에서 데이터 바인딩을 통한 UI 업데이트가 잘 이루어졌음을 확인했다. 일반적으로 데이터 바인딩은 메인 스레드에서만 실행되어야 한다고 생각했지만, 백그라운드 스레드에서도 UI가 잘 업데이트되는 것을 발견하고, 이에 대한 공식 문서를 확인하게 되었다.
공식 문서에 따르면 데이터 모델이 컬렉션이 아닌 경우에만 백그라운드 스레드에서 변경할 수 있다고 한다. 따라서 데이터 모델이 컬렉션이 아닌 경우에는 백그라운드 스레드에서 데이터 바인딩을 통해 UI를 업데이트할 수 있다는 결론을 내릴 수 있다. 이러한 점을 고려하면 스레드 문제는 아니었음을 알 수 있다.
BindingAdapter의 파라미터를 nullable하게 선언하여 null 값이 바인딩 되면서 앱이 크래시 나지 않는가? 🧐
두 번째 검증에서는 놀랍게도 view에 null 값이 들어오지도 않았고 크래시도 나지 않았다. null 값이 들어와서 null 값으로 바인딩이 되는 것이 정상적인 실행 결과라고 생각했는데 아니었다. 아무리 생각해도 이상해서 Thread에서 1초의 시간을 두어 UI가 업데이트되도록 수정했다.
그런데 다음과 같은 현상이 발생했다.
위 영상을 보면, 데이터가 null 값으로 들어왔다가 시간이 지나면서 데이터가 제대로 들어오는 것을 확인할 수 있다. 아마도 메인 스레드에서는 데이터가 들어오지 않았기 때문에 데이터 바인딩을 null 값으로 설정한 후, 시간이 지나면 백그라운드 스레드에서 DB에서 받은 데이터를 데이터 바인딩해준 것으로 보인다.
다른 수사관에게 조언을 구하다. 🙋
나는 레아에게 이게 무슨 상황인지 조언을 구했다. 레아는 데이터 바인딩이 실행되어 구현되는 코드를 보면 도움이 될 것이라고 해주셨고, 나는 데이터 바인딩이 실행되는 내부 코드를 확인해 보았다.
내부 코드를 보면 다음 이미지와 같다.
위 이미지를 보면 Reservation과 theaterName이 nullable한 것을 확인할 수 있다.
범인 검거 😤
그렇다. 데이터 바인딩 프로퍼티 스스로가 범인이었던 것이다. (사실 내가 범인..)
데이터 바인딩 프로퍼티는 nullable하므로, nullable한 것을 고려하여 bindingAdapter를 사용할 때는 해당 파라미터를 nullable하게 선언하여 사용할 필요가 있다. 이렇게 하면 null이 들어왔을 때 예외가 발생하는 상황을 방지할 수 있다.
앞으로는 이를 유념하여 bindingAdapter를 사용할 것 같다.
'Develop > Kotlin' 카테고리의 다른 글
[Android] UI 이벤트 처리 (2) | 2024.10.27 |
---|---|
[Android] 너는 왜 inline Composable이야? (1) | 2024.10.22 |
[Kotlin] 얼렁뚱땅 inline 탐험일지 🧐 (0) | 2024.04.06 |
[Kotlin] 확장 함수 (0) | 2024.04.03 |
[Kotlin] Data class (0) | 2024.04.03 |