Home > Software engineering >  ValueEventListener returning a snapshot with null value for a brief second after .runTransaction()
ValueEventListener returning a snapshot with null value for a brief second after .runTransaction()

Time:10-26

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

Avoiding a null from Database is no big deal, but the fact that the Database is returning a null in the first place is extremely problematic if concurrent transactions are performed(?).

And I believe the issue is in my transaction, I've tried reading some answers in here but they redirect to Firebase documentation that no longer exists(?), and the documentation available is for the Firestore API: https://firebase.google.com/docs/#section-transactions

I believe my issue is the placing of the setValue()'s as all of them are placed inside the null check.

@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);
}

Another important information is that the transaction is entering the same branch, BUT this branch is forked into 3 main sub-branches, so it is a little bit too divided.

Frank Van Puffellen said in a previous answer (Firebase runTransaction not working - MutableData is null):

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

(A Compare And Swap)

So I think that because I did not specified a new Value AND the Database confirmed equality between "assumed" AND "actual" WAY before my null checker actually got the time to do something meaninful (because its placed too late), new value is set to null for a brief second, Then user sets the correct new value with the help of the cached state on device.

The problem with this hypothesis is that the second (correct) write should not perform since assumed and actual should always match since actual is now null and the code beneath the null check should never get executed, since it will never be not null because no code is placed outside the null check.

And whats worst, lines like anObject.setA(anObject.getA() outerDelta); which relay on the use of the previous state are writing the correct answer anyways... which means assumed is never null at that point.

The other reason may be that the null is giving for an entire different reason AFTER the transaction has successfully finished, meaning the transaction is performing well...

Of course, it could always be that there is something wrong with the ValueEventListener written as a reactive component (which I have).

CodePudding user response:

firebaser here

Getting null as the current value in your transaction handler initially is the expected behavior. The transaction handler is called with the current guess of the client for the value, which often will be null. If the initial guess is incorrect, your transaction handler will be called again with an updated guess for the current value. See a.o. https://stackoverflow.com/a/57134276/209103

The solution is to return a value if you get null, even if you know that value will never actually be written to the database:

if (anObject != null) {
    ...

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