Skip to content

Fix blank entry left behind when changing the collection of a new entry - #2418

Open
philipmw wants to merge 1 commit into
TechbeeAT:developfrom
philipmw:fix-blank-entry-on-collection-change
Open

philipmw wants to merge 1 commit into
TechbeeAT:developfrom
philipmw:fix-blank-entry-on-collection-change

Conversation

@philipmw

Copy link
Copy Markdown

This probably fixes issues #2053, #2276, and #2265 (part a). I was experiencing this problem myself intermittently. With this change in a custom build of the app, I have not yet experienced it.

Creating an entry from the list FAB inserts a real row immediately and opens the editor with that row's id baked into the navigation route. Moving the entry to another collection is a copy+delete: the row gets a new id (and UID), while the session keeps several independent references to the old identity (route argument, mainICalObjectId, in-memory object, spinner selection). Any of these going stale turned "discard" into a no-op and left a blank entry in the new collection.

  • DetailViewModel.load(): guard against stale and redundant re-loads. The destination composable re-executes load() with the route's id on every recomposition — also self-triggered seconds after the move by the move's own ContentResolver.notifyChange(NOTIFY_SYNC_TO_NETWORK) causing a sync that updates the collections table and recomposes MainNavHost. Once bound to an id, load() no longer re-binds to a different one, and same-id reloads that would clobber unsaved state are skipped. The internal reload after a move passes force = true.
  • DetailViewModel.delete(): delete the entry that is actually shown (mutableICalObject) instead of mainICalObjectId, which a stale reload can have reverted to the already-deleted original, making the delete a silent no-op.
  • DetailViewModel.deleteById(): switch changeState to LOADING synchronously (all callers are main-thread UI callbacks). Setting it only inside the coroutine allowed a recomposition to still observe the previous state, re-run the discard heuristic and re-open the dialog, making the first "Discard" tap appear to do nothing.
  • DetailsScreen: reset navigateUp when showing the discard dialog, so Cancel no longer re-shows it in a loop on every recomposition.
  • DetailScreenContent: only apply the observedICalObject write-back when the emission belongs to the row currently shown. observeAsState() keeps delivering the previous LiveData's last value until the new emission arrives; right after a move that stale value is the deleted original, and writing its id/uid/collectionId (or sequence) back into the in-memory object reverted the session identity — the subsequent discard then deleted the original again (no-op) and left the blank copy behind.

Assisted-by: GLM 5.3 Flash on Neuralwatt

This probably fixes issues TechbeeAT#2053, TechbeeAT#2276, and TechbeeAT#2265 (part a). I was experiencing
this problem myself intermittently. With this change in a custom build of the
app, I have not yet experienced it.

Creating an entry from the list FAB inserts a real row immediately and
opens the editor with that row's id baked into the navigation route.
Moving the entry to another collection is a copy+delete: the row gets a
new id (and UID), while the session keeps several independent references
to the old identity (route argument, mainICalObjectId, in-memory object,
spinner selection). Any of these going stale turned "discard" into a
no-op and left a blank entry in the new collection.

- DetailViewModel.load(): guard against stale and redundant re-loads.
  The destination composable re-executes load() with the route's id on
  every recomposition — also self-triggered seconds after the move by
  the move's own ContentResolver.notifyChange(NOTIFY_SYNC_TO_NETWORK)
  causing a sync that updates the collections table and recomposes
  MainNavHost. Once bound to an id, load() no longer re-binds to a
  different one, and same-id reloads that would clobber unsaved state
  are skipped. The internal reload after a move passes force = true.
- DetailViewModel.delete(): delete the entry that is actually shown
  (mutableICalObject) instead of mainICalObjectId, which a stale reload
  can have reverted to the already-deleted original, making the delete
  a silent no-op.
- DetailViewModel.deleteById(): switch changeState to LOADING
  synchronously (all callers are main-thread UI callbacks). Setting it
  only inside the coroutine allowed a recomposition to still observe
  the previous state, re-run the discard heuristic and re-open the
  dialog, making the first "Discard" tap appear to do nothing.
- DetailsScreen: reset navigateUp when showing the discard dialog, so
  Cancel no longer re-shows it in a loop on every recomposition.
- DetailScreenContent: only apply the observedICalObject write-back
  when the emission belongs to the row currently shown.
  observeAsState() keeps delivering the previous LiveData's last value
  until the new emission arrives; right after a move that stale value
  is the deleted original, and writing its id/uid/collectionId (or
  sequence) back into the in-memory object reverted the session
  identity — the subsequent discard then deleted the original again
  (no-op) and left the blank copy behind.

Assisted-by: GLM 5.3 Flash on Neuralwatt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant