Как использовать перетаскивание на просмотр recycler с использованием базы данных реального времени firebase

Я пытаюсь реализовать функцию перетаскивания для просмотра регенератора firebase. Недостаточно информации в документах для этой реализации. Я предполагаю, что мне нужно использовать onchildmoved для прослушивателя событий, но я не знаю, как изменить порядок данных.

Ответ 1

У меня была проблема, подобная этому, и сделали следующее (предполагая, что вы используете класс, который расширяет ItemTouchHelper.SimpleCallback, например RecyclerViewTouchHelper):

1) Добавьте свойство к вашему потомку Firebase, чтобы записать его порядок в списке (например, "orderNumber" ).

2) Измените метод onMove() вашего RecyclerViewTouchHelper, чтобы включить следующее:

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {

    final int firstPosition = viewHolder.getAdapterPosition();;
    final int secondPosition = target.getAdapterPosition();
    DatabaseReference firstItemRef = mMovieAdapter.getRef(viewHolder.getAdapterPosition());
    DatabaseReference secondItemRef = mMovieAdapter.getRef(target.getAdapterPosition());

    HashMap<String, Object> updateFirstItemOrderNumber = new HashMap<>();
    updateFirstItemOrderNumber.put("orderNumber", secondPosition);
    firstItemRef.updateChildren(updateFirstItemOrderNumber);

    HashMap<String, Object> updateSecondItemOrderNumber = new HashMap<>();
    updateSecondItemOrderNumber.put("orderNumber", firstPosition);
    secondItemRef.updateChildren(updateSecondItemOrderNumber);

    return false;
}

3) При создании адаптера RecyclerViewAdapter убедитесь, что используемый вами запрос упорядочен по номеру порядка, например.

Query orderedListQuery =  FirebaseRef.orderByChild("orderNumber");

Надеюсь, это поможет!

Ответ 2

У меня была довольно путаница, пытаясь заставить это работать для себя в Firebase UI 3.1.0. Здесь мое окончательное рабочее решение (объяснение ниже):

// 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
}

Я могу делать что-то неправильно, но казалось, что этот ответ выше выше дублирующегося кода, так как FirebaseRecyclerAdapter и наблюдаемые моментальные снимки будут автоматически обновляться при уведомлении. Это привело к тому, что сопротивление резко прекратилось с новыми значениями. Я также получал неправильные номера заказов (возможно, моя ошибка) с помощью getSnapshots(). Get (pos) (моментальные снимки [fromPos] в kotlin). Это вызвало некоторые действительно странные анимации. Также, когда я уведомлялся в движении, мне пришлось перевернуть позиции to/from (target/viewHolder). Однако учтите, что это зависит от заказа в запросе, используя поле заказа. И, наконец, я не хочу, чтобы метод FirebaseRecyclerAdapter # onChildChanged вызывал, если он из-за перетаскивания пользователя, что также вызывает нежелательную анимацию и дублирование усилий.

Ответ 3

Здесь я отправляю решение, которое сработало для меня. Он основан на ответе @prodaea, который имеет правильный подход, но не полностью функциональный. В нем отсутствует обновление базы данных firebase и дополнительная проверка с помощью ChangeEventType.CHANGED, чтобы избежать прерывания анимации при обновлении firebase.

Код живет внутри адаптера, который расширяет FirebaseRcyclerAdapter и реализует интерфейс с помощью одного метода 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")
}