회사에서 작업을 하던 도중 EditText에 관해 화가 나는 상황을 겪었는데요. List 내에 EditText가 존재하는 View가 동적으로 추가할 수도 있고 제거할 수도 있는 그런 류의 아이템을 작업해야 했던 경험이 있습니다. 처음에는 아무 생각 없이 Compose로 작업하다가 Compose 버전이 낮아 한국어 복사 버그 이슈가 있어 다시 xml로 회귀했는데... EditText도 큰 복병이 있을 줄은 몰랐네요.
그래서 오늘 제가 소개할 내용은 EditText를 List 내에서 사용할 경우 발생할 수 있는 문제 중 하나입니다. EditText 내부 텍스트들이 전부 마지막으로 생성된 Item의 EditText의 Text로 덮어쓰여지는 문제인데요. 작업을 하다가 다른 화면으로 이동 후 다시 돌아왔을 때 EditText가 모두 동일한 값으로 변경되어 있는 것을 발견하면서 찾아보고 수정했던 문제입니다. 이와 같은 내용을 제목에 어떻게 담아야할지 한 참 고민했는데, 결국 원하는 내용을 담기는 어렵네요...
긴 글을 읽기에 시간이 없거나 힘드신 분들은 하단 요약을 참고해주세요!
저는 개인적으로 EditText를 List 형태로 사용하는 것을 좋아하지 않습니다. 실제로 어떤 버그가 발생할 지 몰라 권장되지도 않는 방법이기도 하구요. 하지만 회사 기획 요구사항에 EditText를 포함하고 있는 Item이 유저 이벤트를 통해 추가/제거가 되는 것이 존재했습니다. iOS 쪽에서는 아무런 문제제기도 없기도 하고 또 불가능한 건 아니었기 때문에 크게 반박하지는 않고 작업에 들어갔습니다.
처음에는 작업 중에 문제를 확인하지 못했는데요. DataBinding을 통해 UiState의 title 값을 넣어뒀고 TextWatcher를 사용하지 않았기 때문에 uiState의 title 필드 값이 제대로 매핑되어 있었습니다. 더욱이 그 화면에서 다른 화면으로 넘어가는 기능도 없어 문제가 발생하지 않았습니다. 그런데 이 문제가 발생하는 조건은
- Fragment/Activity의 화면이 전환되어 List의 UI가 업데이트 될 것
- 동일한 id 값을 가진 EditText가 2개 이상 존재할 것
- 화면 전환 이전에 EditText에 다른 값이 존재할 것
을 갖추게 되면, 화면 전환 이후 다시 돌아왔을 때 TextWatcher에 마지막으로 생성된 EditText의 Text 값이 모든 TextWatcher에 호출되는 것을 확인할 수 있습니다.
사실 findByViewId나 KTX를 사용할 때는 확연하게 드러나는 문제인데(id를 직접 참조하고 있으므로), DataBinding으로 넘어오면서 Binding이란 Class를 사용하게 되면서 ID 값을 참조하지 않을거라고 판단했지만 EditText 내에서 Text를 다시 넣어주는 과정에서 아직 내부적으로 id 값을 사용중이라는 것을 확인할 수 있었습니다.
그래서 다른 Binding 필드에 들어있는(List 이므로 ViewHolder에 각기 다른 Binding 변수가 존재하므로) 동일한 아이디의 EditText가 내부적으로 동일한 id 값을 가지게 되고, 이는 화면을 재구성할 때 안드로이드 내부적으로 id 값을 통해 기존의 값을 반환해주면서 모든 EditText가 마지막으로 생성된 EditText의 값을 가지게 된다고 합니다.
정리하자면
- xml에서 제작한 EditText에 id 값을 부여하면서
- List 형태의 UI에서 EditText를 여러개 inflate하는 경우
위 조건들을 만족하면 마지막에 생성된 EditText의 Text를 모든 EditText가 따라가는 형태의 고장이 납니다. 이를 해결하기 위해서는 EditText의 id 값을 변경해주어야 하는데요. 이는 위에서 설명했듯 내부적으로 id값을 통해 참조하고 있기 때문입니다.
하지만 EditText는 xml에서 이미 id를 부여받은 값인지라 코드에서 변경해주지 않으면 안됩니다. 저는 여기까지 도달하기 위해 수많은 삽질을 거듭했기 때문에 엄청 애를 먹었는데요(실제로 ChatGPT한테도 원인 파악을 하지 못한 채로 질문을 계속 던지다보니 서로 삽질하는 결과가...). 실제로 문제의 원인을 찾고 나면 그리 어려운 문제는 아닙니다. 다행히 동적으로 id값을 부여할 수 있는
View.generateViewId()
가 있기 때문이죠. 이 코드를 이용해 Binding.editText.id = View.generateId() 형태로 id 값을 수동으로 부여할 수 있습니다. 이 코드를 사용하면 각 EditText가 전부 다른 값으로 인식해 생각했던 방식대로 동작하죠.
줄 글로 주르르 작성했더니 가독성이 떨어지는 느낌이 있어 하단에 요약 형태로 다시 작성해두겠습니다.
요약
문제: EditText가 List 형태로 존재하는 경우 다른 화면으로 이동했다가 Fragment/Activity가 재구성되는 경우 EditText의 모든 값이 마지막으로 생성된 EditText의 Text값이 되는 상황
조건
- EditText의 xml id 값 부여
- List 형태로 작성하기 위해 EditText가 존재하는 xml을 여러개 inlfate(ex: RecyclerView의 ViewHolder와 같은 형태)
- EditText가 여러개 존재하는 문제의 화면이 재구성(화면의 이동 및 오래되어 onCreate가 다시 발생하는 경우)
원인: EditText가 기존에 가지고 있던 text를 내부적으로 id 값을 통해 할당. 이로 인해 화면이 재구성되는 과정에서 id 값으로 매핑이 진행되는데, id값은 Binding을 하더라도 동일한 값을 가질 수 있음. 한 화면 내에 동일한 여러 아이디가 존재함으로서 문제 발생
해결 방안: EditText의 id값을 코드에서 수동으로 변경해주는 작업을 진행. 주로 View.generateViewId를 사용하면 해결
갈 길이 멀다는 생각을 이번 이슈를 통해 하게 되었습니다. 10개가 넘는 앱을 제작하고 경력으로도 만 4년을 넘겼는데도 불구하고 제가 생각지도 못한 이슈가 존재한다는게 아직 햇병아리라고 생각하게 되는 계기였습니다. 개발에 대한 회의감은 덤이지만(새삼스레 제가 검색을 참 못한다는 것도 깨달았구요).
이런 내용을 검색하는 분이 많지는 않겠지만, 누군가 이와 같은 내용으로 고생하는 분이 1명이라도 제 글을 보고 도움이 되었으면 하는 바람과 다음 번에도 동일한 이슈가 생기면 제 블로그를 보겠다는 목적으로 작성해봤습니다.
'안드로이드 > 코틀린' 카테고리의 다른 글
Android PhotoPicker (1) | 2024.08.19 |
---|---|
Glide로 이미지 stroke 만들기 (0) | 2024.05.21 |
Generic 이란 (1) | 2023.12.08 |
Android - 시스템 앱 알림 상태 확인 (0) | 2023.10.25 |
(Kotlin) Flow - 1 (0) | 2023.08.10 |
댓글