Firebase: How to Update Multiple Nodes Transactionally? Swift 3

Firebase: How to update multiple nodes transactionally? Swift 3

In the Firebase documentation in the section Enable Offline Capabilities it is specified that:

Transactions are not persisted across app restarts

Even with persistence enabled, transactions are not persisted across
app restarts.

So you cannot rely on transactions done offline being
committed to your Firebase Realtime Database.

Therefore:

1. there is no way to use firebase transactions at client side to update a value at two or more paths.

2. using a completion callback to perform the second write is not viable since the client could restart the app before a response is received in the completion handler from the firebase server, thus leaving the database in inconsistent state.

I assume that my only option to update data transactionally at the first path and further update the second path with that data that was already written at the first path in Firebase Database, would be to use Conditional Requests over REST as specified in the Firebase Documentation.

This would achieve the same functionality provided by Firebase framework for IOS clients.

    1. The client will make a request via Alamofire to my server (I will use Vapor framework so as to leverage the Swift language), once the request is received at the Vapor server, a GET request will be sent to the Firebase Database server root/users/bookings/4875383 in which I will request an ETAG_VALUE

What is ETAG Value?: ( a unique identifier which will be different every time data changes at the path where GET Request is made. Namely if another user writes to the same path before my write request the resource succeeds, my write operation will be rejected since the ETAG value at the path will have already been modified by the other user's write operation. This would enable users to write data transactionally to a path)


    1. a response is received from the Firebase Server containing a an ETAG_VALUE.

    1. make a PUT request to the Firebase Server and in the header specify the ETag: [ETAG_VALUE] received from the previous GET request. If the ETAG value posted to the server matches the value at the Firebase Server, the write will succeed. If the location no longer matches the ETag, which might occur if another user wrote a new value to the database, the request fails without writing to the location. The return response includes the new value and ETag.

    1. Furthermore, now we can update the value at root/Cleaners/bookings/4875383 to reflect the job that was claimed by a cleaner.

How to update multiple types of nodes in Firebase with one call (Swift)

When you use updateChildValues, the Firebase server will loop over each key in the dictionary and call setValue on each key with the value you specified.

This means that it only merges on the top-level of the dictionary; on each child, it still replaces the existing values.

If you want to perform an update/append on lower levels of the JSON, you will need to encode the path to those children into the keys of the dictionary. So to add a new message to the messages child of the conversation, you will need to have the path for the new message as the key. You're already generating your own message ID, so all that is needed it so include it in the key instead of in the value of the dictionary:

let randomMessageId = NSUUID().uuidString

let values = ["displayMessage": containerView.chatTextView.text,
"lastMessageTime": messageTime,
"messages/"+randomMessageId: messageInfo] as [String: Any]

How to upload new multiple nodes with multiple auto-id

This question is a bit unclear but I think you're asking how to create the 'kardexes' node so it looks like the structure presented in you question.

If so, here's the code that does that

func createKardexesNode() {
let treatmentArray = ["abc", "def", "ghi"]
let ref = self.ref.child("kardexes").child("-LkcSD2KJLwbCj8KAdsd").child("treatments")
for treatment in treatmentArray {
let childRefToAdd = ref.childByAutoId()
childRefToAdd.child("name").setValue(treatment)
}
}

running this code generates a node in Firebase that looks like this

{
"kardexes" : {
"-LkcSD2KJLwbCj8KAdsd" : {
"treatments" : {
"-Ll2dsJivnsH9QRM6MfV" : {
"name" : "abc"
},
"-Ll2dsJivnsH9QRM6MfW" : {
"name" : "def"
},
"-Ll2dsJivnsH9QRM6MfX" : {
"name" : "ghi"
}
}
}
}
}

note that self.ref is a class var that points to the root ref of my Firebase. Substitute your own.

Firebase: How do I update multiple resources atomically?

UPDATE

It's now possible to update multiple locations atomically. See this blog post for details.

var mergedUpdate = {};
mergedUpdate[ 'users/' + userId + '/widgets/' + widgetId ] = true;
mergedUpdate[ 'widgets/' + widgetId ] = widgetData;

var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
ref.update(mergedUpdate);

This doesn't enforce transactional data (if value is currently X, make it Y), but this part can be moved to security rules. For example, if we want to update two counters at the same time, we could add rules as follows:

{
"counter1": {
".validate": "newData.val() === (data.val()||0)+1"
},

"counter2"1 {
".validate": "newData.val() === (data.val()||0)+1"
}
}

Now we can attempt the same multi-path update as above. If the values have changed since we last read them from the server, the attempt will fail. We can check if( error.code === 'PERMISSION_DENIED' ) { ... } to see if the failure was due to validation, and retry accordingly.

ORIGINAL POST

The only way to do this is to run a transaction on a common ancestor.

For instance, if you want to update /a/b/c and /a/x/y, you could run a transaction at /a and change both of the values.

The downside to this approach is that it can be expensive with network I/O, as all of the data inside the transaction needs to be downloaded and then sent back to the server.

A more complicated but potentially more powerful approach you might want to consider is restructuring your data so that rather than storing the actual values, you store a history of the edits. For example, if you're storing bank balance information, you could store a history of deposits and withdrawals. Then when you wanted to get the balance, you would play back the whole history and calculate the end balance.

The beauty of this approach is it lets you do atomic updates. For example, if you were transferring money from accountA to accountB, you'd just append an element to the end of the log saying "transfer from account A to accountB N dollars". Appending that single element is an atomic operation.

This is the approach we take with Firepad, our collaborative text editor.

Flutter Firebase Realtime Database write/update to multiple nodes at once

What you are looking for is known as a multi-path write operation, which allows you to write to multiple, non-overlapping paths with a single update call. You can specify the entire path to update for each key, and the database will then set the value you specify at that specific path.

To generate two separate unique keys, you can call push() twice without any arguments. When called like that, it doesn't actually write to the database, but merely generates a unique reference client-side, that you can then get the key from.

Combined, it would look something like this:

const db = _firebaseDatabase.reference();
const key1 = db.push().key;
const key2 = db.push().key;
const values = {};
values["user_data/"+key1+"/username"] = "aaa";
values["user_history/"+key2+"/username"] = "aaa";
db.update(values);

How to update 2 separate nodes in Firebase Realtime Database

You can use a multi-path update to delete all the data at once like you desire:

private void deleteScene(DataContainers.Scene iScene) {
// Get root database reference
DatabaseReference mDatabase = FirebaseDatabase.DefaultInstance.RootReference;

// Initialize a new list of "path->value" pairs
Dictionary<string, Object> childUpdates = new Dictionary<string, Object>();

// Delete the given scene and all of its models
childUpdates["/Scenes/" + iScene.id] = null;
foreach (var _model in iScene.models)
{
childUpdates["/models/" + _model] = null;
}

mDatabase.UpdateChildrenAsync(childUpdates);
}

Firebase data consistency across multiple nodes

Note that this is now part of the core Firebase API. See this blog post for details.

var mergedUpdate = {};
mergedUpdate[ 'users/' + userId + '/widgets/' + widgetId ] = true;
mergedUpdate[ 'widgets/' + widgetId ] = widgetData;

var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
ref.update(mergedUpdate);

Firebase: Create/Update multiple child nodes atomically

According to Firebase Docs, you don't need the use of a Transaction Operation (i'll explain this later).

In order to save data in multiple nodes at ONE PACKET, I advise you to build a Map<> array in which the Key represents the path, and the Value represents, well, whatever you want to store inside.

Lets say "user1" just became the owner of "pet4", and your dbRereference is pointing on the root directory:

FirebaseDatabase dbRef = FirebaseDatabase.getInstance().getReference();

Map<String, Object> update = new HashMap<>();
update.put("users/user1/pets/pet4", true);
update.put("pets/pet4/owner", "user4");

Later you can run the .updateChildren(Map<,>) and attach a listener.

dbRef.updateChildren(update).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful())
Log.d("Success!");
else
Log.d("Failure");
}
});

In this way if a failure occurrs, your database won't be changed at all.

"Transaction" is a term that Firebase gave for a different operation, which I believe was the cause of your confusion: It is used when working with data that could be corrupted by concurrent modifications, such as incremental counters. For further read, open the link attached at the beginning.



Related Topics



Leave a reply



Submit