0

I am facing the issue when running the runTransaction func to update values, this func deletes values from a class when the classes are different Let me try to explain

its about my userModel class App version 1.0 has user Model with following attributes

-username -uid -followers

App version 1.1 has user Model with following attributes -username -uid -followers

  • status (ADDED)

so the userModel has one additional attribute

when a user using appVersion 1.0 now runs a transaction on a user who uses appVersion 1.1 (e.g when following, I increase the followers by 1), the transaction done by user 1.0 only sets the value it has in its current version, therefore deleting the STATUS of user with version 1.1

my code

API.ref.databaseSpecificUser(id).runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(final MutableData mutableData) {
            final User user = mutableData.getValue(User);

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

            .....

            mutableData.setValue(user);
            return Transaction.success(mutableData);
        }

the

mutableData.setValue(user);

Updates the values but does not set the status as app version 1.0 does not contain "STATUS" in they userModel class

any idea?

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Zash__
  • 293
  • 4
  • 16
  • 1
    The behavior sounds as expected to me: the database has no knowledge of the versions of your app, so when your transaction sets the data at the location, it merely executes that instruction and writes the data. What do you want it to do instead? – Frank van Puffelen Nov 26 '20 at 15:31
  • 1
    It would also really help if we can see the code that is providing the actual data for the transaction, as it may allow us to come up with another way to accomplish what you want. – Frank van Puffelen Nov 26 '20 at 15:32
  • i want to only update the necessary data, not the entire data under this reference. Strangely enough, if do the same with my iOS version, providing the same code, it does not delete any data. This is really bad currently as I may add more attributes to the userModel later but people with older versions will delete them... – Zash__ Nov 26 '20 at 18:05
  • Transactions set the entire value at the location, hence the method being called `setValue`. That should apply on all platforms. If you want to perform a partial update, you'd call `updateChildren` passing in only the children that need to be modified. If you only want to update a single property, I'd recommend running a transaction on just that node/property, instead of on the entire user. If you want to simply increment a value, I recommend using `ServerValue.increment(1)` - which was added to the API earlier this year. – Frank van Puffelen Nov 26 '20 at 22:54
  • I am using runTransaction as it is suggested by firebase when incrementing values. But as I understand correctly, it is not possible updating only the changed values if I use the transaction, correct? thanks for your help! – Zash__ Nov 27 '20 at 11:56

1 Answers1

1

A transaction must provide the complete new value for the location that you run it on. You can't run a transaction on API.ref.databaseSpecificUser(id) and then only provide a value for a single property under it.

You have a few options in your scenario:

  1. Don't use the (incomplete) User class for calling setValue(), but instead only update the specific property directly on the mutableData. So that'd be something like mutableData.child("count").setValue(42). This way the mutableData will still contain all properties, even when the User class doesn't.

  2. Run the transaction directly on the property that you want to modify, so: API.ref.databaseSpecificUser(id).child("count").runTransaction(.... That way the mutableData will only be for the property that you're modifying, so nothing else will be deleted. Here too, you won't be using your User class, as you're reading/writing a single property.

  3. Use the newer ServerValue.increment(1) operation to increment the count. This operation is much more efficient, and typically leads to simpler code. Something like: ServerValue.increment(1).child("count").setValue(ServerValue.increment(1));

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks a lot I did not even know about the new function! I will try to improve my code according to this. One more/last question: How will the ServerValue.increment func change my data consumption? is it less than when I use the runTransaction method? – Zash__ Nov 27 '20 at 19:50
  • So I just tested it again with my .runTransaction func: My android method is deleting the unknown values while my iOS app is not, but do exactly the same. – Zash__ Nov 27 '20 at 20:43
  • I think I know why they act differently. In my iOS version I get the mutable data and pick specific values to change them. Finally I set the value to the new one. In my android version I get the Post.class therefore ignoring values which do not exist. Saving it results in deleting the values which were unknown to the class! Thanks to your reply I found the solution to my problem! – Zash__ Nov 27 '20 at 20:45
  • Glad to hear you figured it out. I tried to explain that the `User` class is causing your problem in options 1 and 2 in my answer. – Frank van Puffelen Nov 28 '20 at 15:00
  • Oh sorry I mixed up Post and user class. Still, is this better on efficiency (data consumptions) when using the solution under point 3 instead of running a transaction (as a general question) – Zash__ Nov 28 '20 at 18:09
  • 1
    If all you need to do is increment a value, the `ServerValue.increment()` operation will be faster than running a transction. For tmore on this, see https://stackoverflow.com/questions/61536203/how-quickly-can-you-atomically-increment-a-value-on-the-firebase-realtime-databa – Frank van Puffelen Nov 29 '20 at 14:23