I'm working on simple notes app and struggling with recyclerview and note deletion. I use DiffUtil and the problem is when I delete the note, it disappears, but the rest of the elements do not pull up, taking up an empty space. It happens only if activity restarded (for example, by changing display orientation).
I've read there are bunch of adapter.notify... methods, but If I use .notifyDataSetChanged() or .notifyItemRemoved(position) I get strange behaviour: the note data deletes, but list item reappears almost immediately after been swiped and deleted
Adapter
class NoteAdapter @Inject constructor() : RecyclerView.Adapter<NoteAdapter.NotesViewHolder>() {
private val differCallback = object : DiffUtil.ItemCallback<NoteEntity>() {
override fun areItemsTheSame(oldItem: NoteEntity, newItem: NoteEntity): Boolean {
return oldItem.id == newItem.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: NoteEntity, newItem: NoteEntity): Boolean {
return oldItem == newItem
}
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotesViewHolder {
val binding =
ItemNoteBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return NotesViewHolder(binding)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun onBindViewHolder(holder: NotesViewHolder, position: Int) {
val item = differ.currentList[position]
holder.binding.apply {
tvTitle.text = item.noteTitle
tvDesc.text = item.noteDescription
// on item click
holder.itemView.setOnClickListener {
onItemClickListener?.let { it(item) }
}
}
}
inner class NotesViewHolder(val binding: ItemNoteBinding) : RecyclerView.ViewHolder(binding.root)
// on item click listener
private var onItemClickListener: ((NoteEntity) -> Unit)? = null
fun setOnItemClickListener(listener: (NoteEntity) -> Unit) {
onItemClickListener = listener
}
}
MainFragmet
@AndroidEntryPoint
class MainFragment : Fragment(R.layout.fragment_main) {
private val viewModel: NoteViewModel by activityViewModels()
lateinit var binding: FragmentMainBinding
@Inject
lateinit var noteRepo: NoteRepo
@Inject
lateinit var noteAdapter: NoteAdapter
@Inject
lateinit var note: NoteEntity
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// SHOW AVAILABLE NOTES USING RV
collectNotes()
initSwipeToDeleteNote()
onClickNote()
// GO TO CREATE NEW NOTE BY PRESSING NEW NOTE BUTTON
val button: FloatingActionButton = view.findViewById(R.id.btnAddNote)
button.setOnClickListener {
findNavController().navigate(
R.id.action_mainFragment_to_addNoteFragment
)
}
}
private fun onClickNote() {
// onclick navigate to add notes
noteAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("note", it)
}
findNavController().navigate(
R.id.action_mainFragment_to_updateNoteFragment,
bundle
)
}
}
private fun collectNotes() {
binding.apply {
if (noteRepo.getAllNotes().isNotEmpty()) {
rvNoteList.visibility = View.VISIBLE
tvEmptyText.visibility = View.GONE
noteAdapter.differ.submitList(noteRepo.getAllNotes())
setupRecyclerView()
} else {
rvNoteList.visibility = View.GONE
tvEmptyText.visibility = View.VISIBLE
}
}
}
private fun setupRecyclerView() = binding.rvNoteList.apply {
adapter = noteAdapter
layoutManager = LinearLayoutManager(activity)
}
private fun initSwipeToDeleteNote() {
// init item touch callback for swipe action
val itemTouchHelperCallback = object : ItemTouchHelper.SimpleCallback(
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder,
): Boolean {
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// get item position & delete notes
val position = viewHolder.adapterPosition
val note = noteAdapter.differ.currentList[position]
viewModel.deleteNoteByID(
note.id
)
Snackbar.make(
binding.root,
getString(R.string.note_deleted_msg),
Snackbar.LENGTH_LONG
)
.apply {
setAction(getString(R.string.undo)) {
viewModel.addNote(
note.noteTitle!!,
note.noteDescription!!
)
}
show()
}
}
}
// attach swipe callback to rv
ItemTouchHelper(itemTouchHelperCallback).apply {
attachToRecyclerView(binding.rvNoteList)
}
}
}
