ValueEventListener 在 .runTransaction() 之后的一秒内返回空值快照

ValueEventListener returning a snapshot with null value for a brief second after .runTransaction()

        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            if (snapshot.getValue() == null) {
                Log.println(Log.ERROR, TAG, "onDataChange: WAS NULL!!!!");
            } else {
                //Do my thing.
            }
        }

从数据库中避免 null 没什么大不了的,但是如果执行并发事务,数据库首先返回 null 的事实是非常有问题的(?)。

而且我认为问题出在我的交易中,我尝试阅读此处的一些答案,但它们重定向到不再存在的 Firebase 文档(?),可用的文档是针对 Firestore API: https://firebase.google.com/docs/#section-transactions

我认为我的问题是 setValue() 的放置,因为它们都放在空检查中。

@NonNull
@Override
public Transaction.Result doTransaction(@NonNull MutableData mutableData) {

    MutableData childA = mutableData.child(Keys.A);
    MutableData childB = mutableData.child(Keys.B);
    MutableData childC = mutableData.child(Keys.C);
    assert outerKey != null;
    MutableData bAReference = childB.child(Keys.Ba).child(dayKey).child(outerKey);
    MutableData bBReference = childB.child(Keys.Bb).child(outerKey);
    MutableData bCReference = childB.child(Keys.Bc).child(dayKey);
    ObjectA anObject = childA.getValue(ObjectA.class);

    if (anObject != null) { /**I think the issue is that I am setting values ONLY IF reading of anObject != null*/
    // Another reason may be that the null check is performed too late when it should at an earlier stage.

        anObject.setA(anObject.getA() + outerDelta); //Change some inner state values.
        anObject.setB(newBaObject.getB());          //Change some inner state values.

        childA.setValue(anObject);                  /**Set value inside != null check(??)*/
        /**^^^^This (childA) is the one returning null for a second*/

        ObjectBc bCObject = Builder.buildSomething(dayKey, bCReference.getValue(ObjectBc.class), outerNewValues); // Builds a new Object with something old and something new

        if (!something) {
            bAReference.setValue(newBaObject);
            bBReference.setValue(newBbObject);
        } else {
            bAReference.setValue(null); //I dont know if these are returning null, but it doesn't
            bBReference.setValue(null); // matter since the one above is returning null anyways

        }

        if (somethingElse || something) {
            creator.accept(childC::child); /**Sequential side effect iterator function for child creation (Don't judge me please! it's supposed to be faster :))*/
        }

        bCReference.setValue(bCObject); /**Again: sets value inside != null check*/


    }


    return Transaction.success(mutableData);
}

另一个重要的信息是交易进入同一个分支,但是这个分支被分叉成3个主要的子分支,所以有点分裂了。

Frank Van Puffellen 在之前的回答中说 ():

If the actual stored value is the same as the assumed current value, the Firebase server writes the new value you specified.

(比较和交换)

所以我认为,因为我没有指定一个新值,并且在我的空检查器实际上有时间做一些有意义的事情之前,数据库确认了“假设”和“实际”方式之间的相等性(因为它放置得太晚了) ,新值被设置为空一秒钟,然后用户在设备上的缓存状态的帮助下设置正确的新值。

这个假设的问题是第二个(正确的)写入不应该执行,因为假设和实际应该总是匹配,因为实际现在是空的,空检查下面的代码永远不会被执行,因为它永远不会被执行not null 因为没有代码放在 null 检查之外。

最糟糕的是,像 anObject.setA(anObject.getA() + outerDelta); 这样依赖于先前状态的行无论如何都会写出正确的答案......这意味着 assumed 永远不会为空那时。

另一个原因可能是在交易成功完成后给出 null 的原因完全不同,这意味着交易执行良好...

当然,也可能是作为响应式组件(我有)编写的 ValueEventListener 有问题。

firebaser 在这里

最初将 null 作为事务处理程序中的当前值是预期的行为。事务处理程序使用客户端当前猜测的值调用,该值通常为空。如果初始猜测不正确,您的事务处理程序将再次调用,并提供对当前值的更新猜测。参见 a.o。

解决方案是 return 如果您得到 null 一个值,即使您知道该值永远不会实际写入数据库:

if (anObject != null) {
    ...

}
else {
    mutableData.setValue("This is needed to get the correct flow"); // 
}

如果您不希望 Firebase 立即为交易值触发本地事件,但仅在确认交易后才触发,您可以将布尔值第二个参数传递给 runTransaction 以表明这一点:

https://firebase.google.com/docs/reference/android/com/google/firebase/database/DatabaseReference#runTransaction(com.google.firebase.database.Transaction.Handler,%20boolean)