BUG: Repeated Saving of Edit Row with out of date data

Hi All,

ISSUE/BUG:

This is a critical issue that I will email to support directly, but thought I check in this wonderful community first if anyone has experienced this issue?

I've noticed that oftentimes a user will edit a row, and will leave the app a fraction of a second too early - before the sync has fully completed.  The edited row, however, is successfully sent to the database.

Later, the same user re-opens the app and the same change is sent again (as if it was still queued on the device and then sent immediately upon re-opening/syncing the app).

There are many examples where this presents a critical issue, in a number of our apps.  One example is:

Contractor timesheet approval and invoicing EXAMPLE:

  1. Client approves one of our contractor's timesheets, but leaves the app a fraction too quickly (change successfully saved to Database, but the devices thinks not)
  2. Later (on a schedule) the app picks up all approved timesheets under each contract and invoices the client in accordance with contract terms.  In short; A BOT creates a Draft Invoice, then Adds all un-billed timesheets ([Invoice] column is blank) to the Draft Invoice as child records - by adding the new draft invoice row-key (ID) to the [Invoice] column on each timesheet, adopting those timesheets as children of the Draft invoice, calculates the invoice value, prints the PDF invoice and emails the invoice and associated timesheets to the client.
  3. Later again, the original approver re-opens the app and the timesheet that was already approved is 're-approved' - row sent to database again as the sync finishes it's last fraction of a second.  Issue here is that when the approver syncs through the change (a repeated save), the approved timesheet row (as it was still stored on the device) is sent to the database with a blank [Invoice] value in the row.
  4. Auto-billing system picks up the approved timesheet again and bills it a second time.... 😳🤯😩 - NOT Good.

SOLUTIONS available (that I am aware of):

1. Add a force-sync to the 'Approve' button that will force the app to sync upon the approver approving the timesheet.  

  • Band-aid solution, not repeatable, not good enough
  • Annoying from a UX perspective if the user has say 5 or 10 timesheets to approve (though we can tell the action not to sync the device unless the timesheet being approved is the last on the list)

2. Add a trigger to the Database (MySQL DB on Google Cloud) that blocks the subsequent row-edit so that the previously inserted Timesheet[Invoice] value cannot be edited back to blank.

  • I have not yet tried this, wondering if others have?
  • My main question here is - will a before edit type trigger stop all edits to the row?  As I understand that AppSheet sends the whole row again to the DB (even when only one column value has changed).  This is not desirable as I don't want a trigger to protect the whole row, only I want it to prevent deletion of the inserted [Invoice] value.  (For example, one still needs to be able to add comments to the Timesheet[Comments] column).

NOT a Solution:

Adding validation that makes the row editable-if = false if the [Invoice] value is inserted.  As the device that has stored the row (as 'not synced by just a fraction of a second') will follow this rule diligently and proceed as there is no value for [Invoice] as far as the device is concerned upon immediately syncing upon re-opening of the app.

Another POTENTIAL Solution??

Can AppSheet change the order of operations upon syncing the device on re-opening? I.e. fetch the correct data from the DB before finishing (in this case, repeating) the save of the locally-stored edited row.

Broadly the issue is.... well, broad:

There are many many examples where this sort of behaviour is indeed catastrophic.... I hope it's an easy bug to fix, so I can avoid going down the MySQL trigger path everywhere I look in all of our apps, as this too is really just a bandaid.

Thanks all...

@Steve @Aleksi @MultiTech @Bellave_Jayaram @JuneCorpuz @LeventK 

4 8 370
8 REPLIES 8

It sounds to me like what you might benefit best from would be to take a step back and review the whole process.

It sounds like there's some part of the process that having this difficulty, so if you can change how you're doing that data interaction process - you can probably smooth out this problem.

 

Thanks @MultiTech I know what you mean and appreciate that.  It’s usually how I solve seemingly complex problems actually - stepping back / up to a higher level of thinking with a wider view…

However the issue exists of row edits being sent to the database more than once, and this is not expected behaviour.  (Really I should cut the original post down to include the first three paragraphs only).

Cheers

So you're saying that if someone closes the app too fast, the app will send the record to the server - but not get the confirmation signal from the server.

  • Which then causes the app to re-send the row again when the app opens?

@Aleksi 

Yup. The audit log shows two identical rows being sent to the server – JSON identical, with the same “timestamp” in each one but different “requestStartTime”

I would think that these would be reconciled on the server with no problem

Indeed they are.  The issue is the second one is at that point, old data that’s missing information that had be updated in between the two sends.  So the second send overwrites data with out of date info – hence the title of this post.

Anyho… just another AppSheet quirk to be aware of and design around (though a pretty dangerous one in critical info systems)

Hmmm... you're on to something here.

Each update sent to the server contains a timestamp, and a BUNCH of other metadata... so the server should recognize that the "duplicate" is an "old" record.

  • Based on this... the newer updates should override, not the other way around; because there's enough data there for the system to see:
  • This is an update to table X row Y with timestamp Z by device D.

Based on that combinations of variables - the server could pick out an old duplicate record like this, and filter it out.

@Arthur_Rallu

 

It should be as follows:

  1. Original record update sent to server
  2. Record received and updated in the cloud
  3. Additional updates made by others made
  4. Duplicate record update sent to the server
  5. Record received, and recognized as old data based on the timestamp

Yep, that’s the one Matt, I agree.

It would appear that “requestStartTime” is being adhered to but it really should be “timestamp” - especially in what is essentially an offline app (working with local data copies).

Having said that, you’ve made me realise that unless AppSheet stores row keys and ‘last update timestamp’s in another database somewhere and checks this each time an update request is received, then it’s not possible to reject ‘outdated’ requests.

So…I’m back to the point of putting an [update timestamp] column into all of my tables, having an initial value of NOW() set and Reset On Edit turned on for such columns, and creating a Trigger on my DB to do the rejecting there… Oh…

Hoping somebody from Google can bump this across to the dev/engineering team…?

Top Labels in this Space