- 자사 서비스에서 Realm DB를 활용하고 있다.
- Realm DB는 빠르고 가벼운 로컬 데이터 저장을 위한 객체 기반 데이터베이스이다.
- Realm DB의 객체로 리스트를 구성할 때는 RealmRecyclerViewAdapter를 이용하여 구성할 수 있다.
- 내부 구성은 일반 RecyclerView와 크게 다르지 않다. 하지만 getItem 등의 함수가 RealmDB 자체에서 오리지널 인스턴스를 가져오는 방식으로 동작한다.
/**
* Returns the item in the underlying data associated with the specified position.
*
* This method will return {@code null} if the Realm instance has been closed or the index
* is outside the range of valid adapter data (which e.g. can happen if {@link #getItemCount()}
* is modified to account for header or footer views.
*
* Also, this method does not take into account any header views. If these are present, modify
* the {@code index} parameter accordingly first.
*
* @param index index of the item in the original collection backing this adapter.
* @return the item at the specified position or {@code null} if the position does not exists or
* the adapter data are no longer valid.
*/
@SuppressWarnings("WeakerAccess")
@Nullable
public T getItem(int index) {
if (index < 0) {
throw new IllegalArgumentException("Only indexes >= 0 are allowed. Input was: " + index);
}
// To avoid exception, return null if there are some extra positions that the
// child adapter is adding in getItemCount (e.g: to display footer view in recycler view)
if(adapterData != null && index >= adapterData.size()) return null;
//noinspection ConstantConditions
return isDataValid() ? adapterData.get(index) : null;
}
- 서비스에서는 스와이프 동작을 하면 지정한 아이템이 물리적으로 삭제되고, 서버에서 푸시가 내려오면 observer 패턴으로 자동 갱신이 되는데 삭제 이후 곧바로 새로운 푸시가 수신되는 상황에서 아주 드물게 중첩 상황으로 인한 오류가 발생하는 것으로 보인다.
- 정확히는 삭제 이후 서버 푸시로 인한 자동 갱신에서 리스트에는 아직 삭제가 반영되지 않은 오리지널 인스턴스가 살아있어 유효성에 대한 Exception을 발생시키는 것으로 확인된다.
- 대응 방법 첫 번째 단계로 먼저 isValid라는 RealmDB에서 해당 인스턴스의 유효성을 검사하는 Boolean 함수를 사용해준다.
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val noti = getItem(position) ?: return
if (noti.isValid) {
} else {
notifyDataSetChanged()
}
- 유효하지 않다면 다시 Adapter의 갱신을 진행한다.
- 대응 방법 두 번째 단계로 onBindViewHolder에서 삭제 처리를 고려하여 안전성을 위해 copyFromRealm()을 사용한다.
val realm = Realm.getDefaultInstance()
val noti = getItem(position) ?: return
if (noti.isValid) {
val notification = realm.copyFromRealm(noti)
- 해당 메서드는 Realm DB로부터 독립적인 복사 객체를 전달하므로 원본에 비해 유효성에 대한 위험성이 없다.
- 만일 삭제 시나리오가 없는 단순 UI 표시 로직에서는 굳이 이 메서드를 사용할 필요는 없다.