Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase how to check transaction success or fail?

I am trying to update a firebase node in transaction, simple stuff. Followed the doc:

https://www.firebase.com/docs/ios/guide/saving-data.html

Firebase* upvotesRef = [[Firebase alloc] initWithUrl: @"https://docs-examples.firebaseio.com/web/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes"];

[upvotesRef runTransactionBlock:^FTransactionResult *(FMutableData *currentData) {
    NSNumber *value = currentData.value;
    if (currentData.value == [NSNull null]) {
      value = 0;
    }
    [currentData setValue:[NSNumber numberWithInt:(1 + [value intValue])]];
    return [FTransactionResult successWithValue:currentData];
}];

The big question is: How do I check the outcome of this transaction(success/fail)? I wish to make some UI changes depends on the outcome of it.

There is another method in SDK document appears to have a callback, but it did not explain which value should I check against. And it says something about the method could be run multiple times. How do I make sure when it gives the "final" result? https://www.firebase.com/docs/ios-api/Classes/Firebase.html#//api/name/runTransactionBlock:andCompletionBlock:

Sorry I am such a beginner that apple style doc really doesn't come together without some examples.

like image 634
Reed Avatar asked Sep 27 '14 03:09

Reed


1 Answers

If you just want to wait for the final value, runTransactionBlock:andCompletionBlock: is the method you want to look at. Here's some example code:

[upvotesRef runTransactionBlock:^FTransactionResult *(FMutableData *currentData) {
    NSNumber *value = currentData.value;
    if (currentData.value == [NSNull null]) {
        value = 0;
    }
    [currentData setValue:[NSNumber numberWithInt:(1 + [value intValue])]];
    return [FTransactionResult successWithValue:currentData];
} andCompletionBlock:^(NSError *error, BOOL committed, FDataSnapshot *snapshot) {
    if (error) {
        NSLog(@"Error: %@", error);
    }
    if (committed) {
        NSLog(@"Data committed");
    }
    NSLog(@"Final Value: %@", snapshot.value);
}];

That last value there, snapshot.value is where you can get your final value. It's the same sort of FDataSnapshot that you get if you use observeEventType:withBlock:

If something goes wrong, you'll get an error.

If your data is committed, committed will be YES. If you returned [FTransactionResult abort] in your transaction block instead of [FTransactionResult successWithValue:], committed will be NO.

This means, if you read the counter at 4 and try to update it. You might try to update the counter at the same time someone else does. If yours get in first, snapshot.value will be 5. If the other person's update gets in before you do, the snapshot.value will be 6.

You probably wanted to get up to 6 no matter who upvoted first. To do this, you need to add an observer. The code for that might look like:

[upvotesRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
    NSLog(@"New Value: %@", snapshot.value);
}];

With this, you don't need the completion block to find out the final value because every time the transaction blocks, the observer block will fire. In the example scenario above, it would fire once for 5 and once for 6 no matter who upvoted first. You do need the completion block if you want to find out whether or not your particular transaction succeeded and not just what value is at that location now.

And, just to be complete, there is one more method called runTransactionBlock:andCompletionBlock:withLocalEvents:. If others are also writing to the same location, the transaction block may run multiple times. This happens if it finds out that it is running on stale data. When it runs successfully on fresh data, it will call the completion block. However, you'll find that each time it runs, it will fire any observer blocks at that location. If you don't want this to happen, you should pass NO to withLocalEvents:. Your Firebase will trigger events at that location whenever a confirmed write goes through, but your local transaction's temporary writes, which are unconfirmed, will not.

Looking back to the example where you and another person are trying to upvote at the same time. By default, the observer will fire as soon as you try to update the count from 4 to 5. The actual transaction might fail because someone else pushed the upvote count from 4 to 5 at the same time. Your transaction block would then run again with the new data, 5, and see that it should push the count to 6. With local events set to NO, the observer will fire after the server lets you know someone else pushed the upvote count from 4 to 5, and not when you try to update the count from 4 to 5.

This isn't a huge deal with something simple like upvotes where everyone is just incrementing, but if you could potentially be pushing different data from other users, any observers at the location may see the data jump around before finally settling.

like image 174
katfang Avatar answered Sep 22 '22 12:09

katfang