ValueEventListener在. 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()'s的位置,因为它们都被放置在null检查内。

@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在之前的回答中说(Firebase runTransaction不工作- MutableData为null):

如果实际存储值与假定的相同。当前值,Firebase服务器写入您指定的新值。

(A Compare And Swap)

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

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

最糟糕的是,像anObject.setA(anObject.getA() + outerDelta);这样依赖于使用前一个状态的行无论如何都在写正确的答案…这意味着假定永远不会为空。

另一个原因可能是在事务成功完成后,null给出了一个完全不同的原因,这意味着事务执行得很好…

当然,也可能是ValueEventListener作为响应式组件编写的时候出了问题(我就是这么做的)。

firebase here

最初将null作为事务处理程序中的当前值是预期的行为。事务处理程序将使用客户机当前对该值的猜测来调用,该值通常为空。如果初始猜测不正确,将再次调用事务处理程序,并对当前值进行更新的猜测。参见a.o. https://stackoverflow.com/a/57134276/209103

解决方案是返回一个值,如果你得到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 % 20布尔)

最新更新