에러 상황
이전에 위와 같은 화면을 구현하는 방법을 포스팅 했었는데, 위의 기능을 구현하면서 발생했던 에러에 대해서 적어보고자 한다.
에러 로그
검색을 하는 도중에 Incosistency detected. Invalid item postion ~~ 에러가 발생하면서 앱이 죽는 문제가 발생했다.
대충 어떤 부분에서 에러가 나는지 짐작은 되는데 정확히는 모르겠다. 이번 에러의 원인은 두 가지인 것으로 추측 된다.
추측 원인
기존 코드
- 아래 코드에서 위와 같은에러가 발생해서 두 번째 코드로 수정을 했었다.
filteredLIst =
if (userInput.isEmpty()) arrayListOf()
else {
val filteringList = ArrayList<Medicine>()
for (item in unFilteredList) {
if (item.itemName.contains(userInput)) filteringList.add(item)
}
filteringList
}
- 필터링된 리스트가 순간적으로 갱신 되면서 아이템을 참조하는 순간과 리스트가 비어있는 순간이 겹치면서 에러가 발생할 수도 있겠다라는 생각을 했기에 아래와 같이 코드를 수정했었다.
수정 코드
- 수정한 코드에서 어느정도 잘 동작하는 듯 싶었으나, 약품 검색에서는 잘 동작했지만, 병원 검색에서는 아래 코드로도 같은 오류가 발생했다.
- 처음엔 기존 코드에서 filteredList에 대입하는 과정이 문제라 생각했다.
- 대입하는 과정에서 기존 인스턴스가 일시적으로 사라지나 싶었지만, 아래 코드로도 오류가 나는 것을 보니 대입이랑은 상관 없는 것 같다.
val newFilteredList =
if (userInput.isEmpty()) arrayListOf()
else {
val filteringList = ArrayList<Medicine>()
for (item in unFilteredList) {
if (item.itemName.contains(userInput)) filteringList.add(item)
}
filteringList
}
filteredList.clear() // 문제의 부분
filteredList.addAll(newFilteredList)
- 대입이 문제가 아니라면 아마 filteredList.clear() 부분이 문제인 것으로 추측된다.
- 인스턴스가 사라지는게 아니라 리스트가 순간적으로 비는 것이 그나마 제일 가능성 높은 원인이었기에 이 부분을 고쳐보고자 했다.
해결 코드
override fun getFilter(): Filter {
return object : Filter() {
override fun performFiltering(p0: CharSequence?): FilterResults {
// 필터링을 수행하는 부분에서는 기존 리스트를 건드리지 않는다.
userInput = "$p0"
val newFilteredList =
if (userInput.isEmpty()) arrayListOf()
else {
val filteringList = arrayListOf<Hospital>()
for (item in unFilteredList) {
if (item.dutyName.contains(userInput)) filteringList.add(item)
}
filteringList
}
// 필터링의 결과를 새 리스트에 담아서 리턴한다.
return FilterResults().apply { values = newFilteredList }
}
@SuppressLint("NotifyDataSetChanged")
override fun publishResults(p0: CharSequence?, p1: FilterResults?) {
// 결과를 발행할 때, 기존의 리스트에 필터링된 결과를 넣어준다.
filteredList = p1?.values as ArrayList<Hospital>
notifyDataSetChanged()
}
}
}
- 기존 코드는 필터링을 하는 동시에 filteredList의 값을 변경했었는데, 이 부분에서 문제가 발생한 것이 아닌가 싶다.
- 필터링을 할 땐 새로운 리스트를 만들어서 결과를 담고, 결과를 발행할 때 기존 리스트에 대입을한다.
- 위의 코드로는 일단 잘 동작하는데 좀 더 테스트해보면서 지켜봐야할 것 같다.
기타 에러
@SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if(filteredList.size <= position) return
val hospital = filteredList[position] // -> 문제의 부분 IndexOutOfBoundsException 발생
holder.hospitalTitle.apply {
text = hospital.dutyName
val matchedIdx = text.indexOf(userInput, 0, true)
if(matchedIdx >= 0){
setForegroundColor(resources.getColor(R.color.primary_normal), matchedIdx, matchedIdx + userInput.length)
}
}
holder.address.text = "[${hospital.dutyAddress}] "
}
- 위의 에러와 직접적인 연관은 없는 것 같지만, try-catch 문으로 묶어서 테스트했을 때, filteredList에 접근하는 과정에서 IndexOutOfBoundsException이 발생했었다.
- 필터링된 리스트가 갱신 되면서 onBindViewHolder 메소드에서 참조하는 filteredList가 순간적으로 비게 되어 에러가 발생할 수 있을 것으로 예상이 된다.