How to handle errors during nested callbacks in Firebase Realtime Database?

I'm using Firebase Realtime Database and I need to do multiple operations in the database, some of that depending on the result of the previous, creating the famous 'callback hell'. How can i handle errors when, for example, the second call goes wrong, but the first one succeeded ?

I tried to find some "Firebase realtime database transaction" (like transactions in mysql or postgres), but didn't find any good examples.

mDb.getReference("users").setValue("someValue").addOnCompleteListener(new OnCompleteListener<Void>() {
        @Override
        public void onComplete(@NonNull Task<Void> task) {

            if(task.isSuccessful()){

              mDb.getReference("services").setValue("someValue2").addOnCompleteListener(new OnCompleteListener<Void>() {
                    @Override
                    public void onComplete(@NonNull Task<Void> task) {

                        if(task.isSuccessful()){

                            mDb.getReference("stores").setValue("someValue3").addOnCompleteListener(new OnCompleteListener<Void>() {
                                @Override
                                public void onComplete(@NonNull Task<Void> task) {

                                    // Here, for some reason, the value "someValue3" could not be set.     
                                }
                            });
                        }
                    }
                });
            }
        }
    });

I need that, if some operation goes wrong, to revert ('rollback') the values set before. Right now, if the transaction on reference 'stores' fail, the values set on 'users' and 'services' will keep on the database.

1 answer

  • answered 2019-06-17 14:15 Frank van Puffelen

    If you want to update multiple values in the database in one call, use a multi-location update that André mentioned in his comment.

    With a multi-location update, your entire code can be reduced to:

    Map<String, Object> values = new HashMap<>();
    values.put("users", "someValue");
    values.put("services", "someValue2");
    values.put("stores", "someValue3");
    mDb.updateChildren(values).addOnCompleteListener(new OnCompleteListener<Void>() {
        @Override
        public void onComplete(@NonNull Task<Void> task) {
            // All writes either completed, or none happened
        }
    });
    

    Some notes:

    • The keys in the values map can contain entire paths, so if you want to update the name of a specific user it could be values.put("users/uidOfTheUser/name", "new name").
    • If you need to write a new value based on the existing value of a node, you will need to use transactions instead. But note that transactions across multiple top-level nodes tend to be highly contentious, so I'd recommend trying to stay away from them in your current use-case.