Firebase 事务:下载数据时以及为什么在事务 运行 时每个 ValueEventListener 都被调用为 null

Firebase Transaction: When data is downloaded and why every ValueEventListener get called with null when a transation is running

我有 2 个问题与实时数据库中的 Firebase 事务有关。用一个例子来解释会更容易。这只是一个例子,不是我的真实代码。所以,如果有一些编译错误,请不要担心。

假设我有一栋建筑。在这栋楼里,有一些数据。我有一系列的楼层。每个楼层都可以有一个计数器,记录该楼层有多少张椅子。客户可以有很多楼层,所以我不想加载所有楼层。我只是为这个客户加载我需要的那些。我需要知道总共有多少把椅子,即使我没有加载它们。规则可能如下所示:

"...": 
{

    "building":
    {
        "...": 
        {

        },
        
        "totalNbChairs": 
        {   
          ".validate": "newData.isNumber()"
        },
        
        "floors": 
        {  
             "$floorId": 
            {
                "nbChairs": 
                {   
                  ".validate": "newData.isNumber()"
                },
                
                "...": 
                {

                },
            },
        },
    },
},

正如我所说,这只是一个例子,不是我的实际代码。不用担心代码问题。

我的客户可以在多个设备上连接,所以我需要在楼层更改他的“nbChairs”时使用事务来调整“totalNbChairs”。重要的是,我需要设置地板上椅子的实际数量,而不仅仅是减少一个值。如果一个楼层的“nbChairs”为10并且客户端同时在2个设备上设置“8”,我不能同时在两个设备上做“-2”​​。

交易看起来像这样

void SetNbChair(String floorId, long nbChairsToSet){
    FirebaseDatabase.getInstance().getReference()
    .child("...")
    .child("building")
    .runTransaction(new Transaction.Handler() {
            @Override
            public Transaction.Result doTransaction(MutableData mutableData) {
                
                //first I need to know how many chairs I have right now on the floor
                MutableData nbChairMutableData = mutableData.child("floors").child(floorId).child("nbChairs");
                Long nbChairLong = (Long)nbChairMutableData.getValue();
                long nbChair = 0;
                if(nbChairLong != null){
                    nbChair = nbChairLong;
                }
                
                long diff = nbChairsToSet - nbChair;
                
                //now I can update the number of chair in the floor
                nbChairMutableData.setValue(nbChairsToSet);
                
                //Update the totalNbChairs
                MutableData totalNbCHairsMutableData = mutableData.child("totalNbChairs");
                Long previousTotalChairLong = (Long)totalNbCHairsMutableData.getValue();
                long totalChair = 0;
                if(previousTotalChairLong != null){
                    totalChair = previousTotalChairLong;
                }
                
                totalChair += diff;
                
                //update the value
                totalNbCHairsMutableData.setValue(totalChair);
                
            }
            
             @Override
            public void onComplete(DatabaseError databaseError, boolean committed,
                                   DataSnapshot currentData) {

                ...
            }
        });
}

我的第一个问题是:什么时候下载我需要在客户端获取的数据?因为就我所见,可能会发生两件事。

首先,当我这样做时

FirebaseDatabase.getInstance().getReference()
    .child("...")
    .child("building")
    .runTransaction

Firebase 可能会下载 (".../building") 中的所有内容。如果是这样的话,这对我来说是非常糟糕的。我不想下载所有内容。所以如果这次交易的所有数据都被下载了,这就真的很糟糕了。如果是这种情况,有没有人知道我如何在不下载所有楼层的情况下进行此交易?

但是,当我这样做时,Firebase 也有可能下载数据

Long nbChairLong = (Long)nbChairMutableData.getValue();

在这种情况下,它要好得多但并不完美。在这种情况下,我需要下载“nbChairs”。等待得到它。之后,我需要下载“totalNbChairs”并等待获取。如果 firebase 在我们执行 getValue() 时下载数据。我们可以在一次调用中批量处理我需要的所有 getValue 以避免等待两次下载吗?

但我可能错了,firebase 做了其他事情。有人可以向我解释一下 firebase 什么时候下载到客户端的内容,这样我就不会大吃一惊了吗?

现在是第二个问题。我实现了我展示的版本。但是在我知道第一个问题的答案之前我不能使用它。但是,我还是做了一些测试。很快,我发现如果数据库中有数据,我的“事务”回调会得到一些“空”事件。好的,文档说这是预期的行为。好的,没问题。我保护了我的代码,我有这样的东西

if(myMandatoryData == null){
    return Transaction.success(mutableData);
}

而且,是的,第一次,我早期的 return 被调用并调用了函数。数据第二次有效。好吧,看起来不错,但是……还有一个大问题!我注意到一些非常糟糕的事情。值得一提的是,我有一些活动的 ValueEventListener 可以知道数据库中的数据何时更改,所以我有一些像这样的东西

databaseReference.addValueEventListener( new ValueEventListener(){

            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                    ...
           
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                ...
            }
        } );

在我早期 return 之后,每个 ValueEventListener 上的每个“onDataChange”都被调用为“null”。所以,我在回调中的代码处理这个就像数据被删除一样。所以,我在 Ui 中得到了意想不到的结果,就像有人删除了我的所有数据一样。当事务重试并获得数据时,“onDataChange”将与有效数据一起被调用。但直到它出现,我的 UI 才显示数据库中没有任何内容。当我开始一个简单的交易时,我应该取消每个 ValueEventListener 吗?这似乎很糟糕。我不想全部取消。另外,我不想在交易完成后重新启动它们后重新下载所有数据。当每个 ValueEventListener 中的事务为 运行 时,我不想添加一个 hack 来忽略​​已删除的数据。当交易 运行 时,如果某些数据真的从另一台设备上删除,我可能会错过。此时我应该做什么?

谢谢

当你执行这段代码时:

FirebaseDatabase.getInstance().getReference()
    .child("...")
    .child("building")
    .runTransaction

Firebase 将读取 .../building 路径下的所有数据。因此,建议 运行 事务在 JSON 树中尽可能低。如果这不是您的选择,请考虑 运行 在 Cloud Functions 之类的东西中设置事务(甚至可以将 maxInstances 设置为 1)以减少事务的争用。


第二个问题:这是预期的行为。您的事务处理程序会立即被调用,其中包含客户端对节点当前值的最佳猜测,在大多数情况下将为 null。虽然这在您的用例中可能永远不可能,但您仍然必须通过返回值来处理 null

有关此内容的详细说明,请参阅:

  • Strange behaviour of firebase transaction