Im trying to implement the drag and drop feature to firebase recycler view. There is not enough information in the docs for this implementation. Im assuming i have to use onchildmoved for the event listener but i do not know how to reorder the data.
Here I post solution that worked for me. It's based on @prodaea's answer, which has right approach but is not fully functional. It's missing firebase database update and additional check against ChangeEventType.CHANGED to avoid the animation interruption when firebase updates.
Code lives inside adapter which extends FirebaseRcyclerAdapter
and implements interface with a single method onItemMove(fromPosition: Int, toPosition: Int): Boolean
:
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
snapshots[fromPosition].sort = toPosition.toDouble()
snapshots[toPosition].sort = fromPosition.toDouble()
// update recycler locally to complete the animation
notifyItemMoved(fromPosition, toPosition)
updateFirebase(fromPosition, toPosition)
return true
}
private fun updateFirebase(fromPos: Int, toPos: Int) {
// flag which prevents callbacks from interrupting the drag animation when firebase database updates
hasDragged = true
val firstPath = getRef(fromPos).getPath()
val secondPath = getRef(toPos).getPath()
Log.d(_tag, "onItemMove: firstPath: $firstPath, secondPath: $secondPath")
val updates = mapOf(
Pair(firstPath + "/sort", snapshots[fromPos].sort),
Pair(secondPath + "/sort", snapshots[toPos].sort))
getRef(fromPos).root.updateChildren(updates)
Log.d(_tag, "updateFirebase, catA: \"${snapshots[fromPos]}\", catB: \"${snapshots[toPos]}\"")
}
private fun DatabaseReference.getPath() = toString().substring(root.toString().length)
override fun onChildChanged(type: ChangeEventType,
snapshot: DataSnapshot,
newIndex: Int,
oldIndex: Int) {
Log.d(_tag, "onChildChanged:else, type:$type, new: $newIndex, old: $oldIndex, hasDragged: $hasDragged, snapshot: $snapshot")
when (type) {
// avoid the drag animation interruption by checking against 'hasDragged' flag
ChangeEventType.MOVED -> if (!hasDragged) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
ChangeEventType.CHANGED -> if (!hasDragged) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
else -> {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
}
}
override fun onDataChanged() {
hasDragged = false
Log.d(_tag, "onDataChanged, hasDragged: $hasDragged")
}
I had quite a bit of confusion trying to get this to work for myself on Firebase UI 3.1.0. Here's my final working solution (explanation below):
// overrides from ItemTouchHelper
override fun onMove(recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder): Boolean {
val fromPos = viewHolder.adapterPosition
val toPos = target.adapterPosition
val fromSnapshot = snapshots.first { it.order == fromPos }
val toSnapshot = snapshots.first { it.order == toPos }
Logger.d("Habit ${fromSnapshot.name} (${fromSnapshot.order}) to $toPos")
Logger.d("Habit ${toSnapshot.name} (${toSnapshot.order}) to $fromPos")
fromSnapshot.order = toPos
toSnapshot.order = fromPos
hasDragged = true
notifyItemMoved(toPos, fromPos)
return true
}
// overrides from FirebaseRecyclerAdapter
override fun onChildChanged(type: ChangeEventType?, snapshot: DataSnapshot?, newIndex: Int, oldIndex: Int) {
when (type) {
ChangeEventType.MOVED -> if (!hasDragged) {
super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
else -> super.onChildChanged(type, snapshot, newIndex, oldIndex)
}
}
override fun onDataChanged() {
hasDragged = false
}
I may be doing something wrong, but it seemed that this answer above above code duplicated effort since the FirebaseRecyclerAdapter and the observable snapshots will automagically update on notification. This caused the drag to end abruptly with new values. I was also getting wrong order numbers (probably my fault) by using getSnapshots().get(pos) (snapshots[fromPos] in kotlin). This caused some really weird animations. Also when notifying on the move, I had to reverse the to/from (target/viewHolder) positions. However, please note this does rely on ordering in the query using the order field. And finally, I don't want to let the FirebaseRecyclerAdapter#onChildChanged method to call if it's because of a user drag, that also causes unwanted animation and duplicated effort.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With