Firebase onChildRemoved()和removeValue()在对时间敏感的操作上起作用的问题

时间:2018-06-24 17:43:17

标签: java android firebase firebase-realtime-database

我有一个可以为某些物品提供报价的应用程序。想法是要约由我添加到我创建的另一个应用程序中,然后同时显示在用户设备上。

我只需要一个用户就可以接受要约,因此当第一个用户单击时,我确实要对要约ref调用removeValue()。已从数据库和其他用户recyclerview中正确删除了报价。

问题是,删除要约的同时发生2次单击,但是onChildRemoved()没有时间被调用,因此两个用户现在都拥有相同要约!

还有其他想法可以使此操作更精确,更省时吗?

根据svi.data的建议,

更新,我在用户单击时尝试了这段代码,但是仍然出现相同的问题。

    offerUnAnsweredRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

            boolean stillThere = false;

            for (DataSnapshot offerSnap : dataSnapshot.getChildren()) {

                if (offerSnap.getKey().equals(requestedOffer.getCurrentNodeKey())) {
                    stillThere = true;
                }
            }

            if (stillThere) {
                Timber.d("We have it " + requestedOffer.getEmployeeKey());
                Toast.makeText(getContext(), "Welcome Dear ", Toast.LENGTH_SHORT).show();
                offerUnAnsweredRef.child(requestedOffer.getCurrentNodeKey()).removeValue();
            } else {
                Toast.makeText(getContext(), "Go Away Bear", Toast.LENGTH_SHORT).show();

            }
        }

        @Override
        public void onCancelled(@NonNull DatabaseError databaseError) {

        }
    });

更新2

实际上,该解决方案是在svi.data答案的基础上进行一些修改的,所以我想共享工作代码以帮助任何遇到类似情况的人

offerUnAnsweredRef.child(requestedOffer.getCurrentNodeKey()).runTransaction(new Transaction.Handler() {
        @NonNull
        @Override
        public Transaction.Result doTransaction(@NonNull MutableData mutableData) {


            RequestedOffer o = mutableData.getValue(RequestedOffer.class);
            if (o == null) {
                return Transaction.abort();
            }

            if (o.getEmployeeKey() == null) {
                o.setEmployeeKey(employee.getUid());
                mutableData.setValue(o);
                return Transaction.success(mutableData);

            } else {
                return Transaction.success(mutableData);
            }
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean b,
                               DataSnapshot dataSnapshot) {
            // check if the transaction completed successfully
            // or if it failed
            RequestedOffer o = dataSnapshot.getValue(RequestedOffer.class);
            if (o.getEmployeeKey() == employee.getUid()) {
                getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), "Hello", Toast.LENGTH_SHORT).show());
                DatabaseReference databaseReference = FirebaseFactory.getDatabase()


            } else {
                getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), "NO", Toast.LENGTH_SHORT).show());
            }
        }

因为文档中所述

  

公共抽象Transaction.Result doTransaction(MutableData currentData)

     

可能会多次调用此方法,并在此位置使用当前数据。它负责检查这些数据并返回一个Transaction.Result,以指定该位置所需的新数据或应该中止该事务。

所以我添加了代码以检入onComplete以确保它仅调用一次。

1 个答案:

答案 0 :(得分:2)

据我了解:

1)您有一个特定的应用程序(由您添加)。

2)您有另一个应用程序(按用户)读取优惠。

3)如果是这种情况,则两个应用程序都使用同一项目。

4)用户单击要约时,他/她将获得要约,然后您将从数据库中删除要约。

5)现在,当2个用户单击相同的商品时,没有时间将该商品从另一个用户的列表中删除,因此他们最终得到了相同的商品。

现在看来您不希望用户获得相同的报价,而问题确实是时间问题。

可能的解决方案:

1)每当用户单击要约时,就对数据库中的offers节点运行ValueEventListener()并检查要约是否存在。

2)如果存在报价,请给他/她报价并将其删除。

3)现在,当2个用户单击同一报价时,我所讨论的ValueEventListener将为您提供一些时间,然后做出响应。

4)因此,用户不应最终获得相同的优惠。

希望它可以解决您的问题。

更新:

由于这是用户之间的竞争条件,因此该谈论交易了。 Firebase提供了一种直接读取并写入同一节点的并发更新的好方法(这就是您的情况)。

我希望您的数据库是这样的:

Offers
|
|------offer_id_1
       |
       |-----taken:false
       |-----......
       |-----......
|
|-------offer_id_2
       |
       |------taken:false
       |------......
       |------......

让我解释一下上面的结构,您从其他应用程序发布的每个报价在默认情况下都将具有一个名为taken的标志,并且在默认情况下其值应为false

现在您在offer_id_1offer_id_2上方看到的是为商品提供的推送ID或随机ID(当用户单击商品时,您必须获得此密钥的引用...我以为您知道该怎么做)。

在我们开始课程之前,您应该为您的帖子提供一个模型课程,我们将其称为Offer,它只是一个课程:

public class Offer{

public boolean taken;
......
......


}

以下功能是您在有人点击要约(我们将使用交易)后将调用的功能:

public void RunTransactionFor(String offer_id){


//first refer to your offers
DatabaseReference offers_ref = FirebaseDatabase.getInstance().getReference().child("offers").child(offer_id);

//run a transaction (a transaction is fast it reads and writes directly)

offer_ref.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            //this is a ref to the offer class 
            Offer offer = mutableData.getValue(Offer.class);

            if (offer == null) {
                return Transaction.success(mutableData);
            }
            if(offer.taken == false){
             //take the offer
             offer.taken = true;
             //show a message
             Toast.makeText(context, "you took the offer",...).show();
             //now you can remove the offer
             offers_ref.setValue(null);//or delete it your way 



            }else{
             //too late the offer is taken
             Toast.makeText(context, "too late the offer is gone",...).show(); 
             //do nothing  
            } 

            // Set value and report transaction success
            mutableData.setValue(offer);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean b,
                               DataSnapshot dataSnapshot) {
            // Transaction completed 

        }
    });

}

现在,当用户单击列表中的要约时,请存储要约的ID,并将其传递给上述功能

//after user clicks

String offer_id = .......

//run transaction

RunTransactionFor(offer_id);

注意:交易只能在线工作,而不能离线工作。