Search code examples
androidkotlinandroid-recyclerviewadapterandroid-diffutils

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter when using DiffUtil


Below is an adapter class for loading all book items in recycler view. Used DiffUtils so that the whole list doesn't get notified.


class BooksAdapter(var mContext: Context) : RecyclerView.Adapter<BooksAdapter.BooksViewHolder>() {

    var books: List<BookTable> = ArrayList()
        set(value) {
            val result = DiffUtil.calculateDiff(BookListDiffCallBacks(ArrayList(value), ArrayList(books)))
            field = value
            result.dispatchUpdatesTo(this)
        }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BooksViewHolder {
        val binding = DataBindingUtil.inflate<ItemBookBinding>(LayoutInflater.from(mContext), R.layout.item_book, parent, false)
        return BooksViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return books.size
    }

    override fun onBindViewHolder(holder: BooksViewHolder, position: Int) {
        holder.setData(books[position])
    }

    inner class BooksViewHolder(var binding: ItemBookBinding) : RecyclerView.ViewHolder(binding.root) {
        fun setData(bookTable: BookTable) {
            binding.book = bookTable
        }
    }
}

My DiffUtil class is as below. I have extended DiffUtil class and checked if items are same or not , if not then DiffUtil will handle updatin

class BookListDiffCallBacks(var oldBooks: ArrayList<BookTable>, var newBooks: ArrayList<BookTable>) : DiffUtil.Callback() {

    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldBooks[oldItemPosition].bookId == newBooks[newItemPosition].bookId
    }

    override fun getOldListSize(): Int {
        return oldBooks.size
    }

    override fun getNewListSize(): Int {
        return newBooks.size
    }

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return oldBooks[oldItemPosition] == newBooks[newItemPosition]// == means structural equality checks in kotlin
    }

    override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
        return super.getChangePayload(oldItemPosition, newItemPosition)
    }
}

Model class



@Entity(
    tableName = "book_table"//,
//    foreignKeys = [ForeignKey(entity = CategoryTable::class, parentColumns = arrayOf("categoryId"), childColumns = arrayOf("categoryId"), onDelete = ForeignKey.CASCADE)]
)
class BookTable : BaseObservable() {

    @Bindable
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "bookId")
    var bookId: Int = 0
        set(value) {
            field = value
            notifyPropertyChanged(BR.bookId)
        }

    @Bindable
    var name: String? = null
        set(value) {
            field = value
            notifyPropertyChanged(BR.name)
        }

    @Bindable
    var categoryId: Int? = null
        set(value) {
            field = value
            notifyPropertyChanged(BR.categoryId)
        }

    override fun equals(other: Any?): Boolean {

        if (other === this) return true // means same reference
        if (other !is BookTable) return false// is not of same class

        // other is now Booktable instance as above condition false if you remove that condition then it will show error in below condition
        // as it has passed above condition means it is of type BookTable
        return (bookId == other.bookId
                && categoryId == other.categoryId
                && name == other.name)

    }

    override fun hashCode(): Int {
        var result = bookId
        result = 31 * result + (name?.hashCode() ?: 0)
        result = 31 * result + (categoryId ?: 0)
        return result
    }

}

My app gets crash and I am getting this error:

java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionBooksViewHolder

I have implemented DiffUtil class so that whole recycleview view items don't get notified.

Reference: https://developer.android.com/reference/kotlin/androidx/recyclerview/widget/DiffUtil


Solution

  • Your parameters to the BookListDiffCallBacks constructor are backwards:

    val result = DiffUtil.calculateDiff(BookListDiffCallBacks(ArrayList(value), ArrayList(books)))
    

    You're passing new, old, but the constructor expects old, new.