Skip to content

android: optimistic UI for task complete and skip#308

Merged
dkhalife merged 2 commits intomainfrom
opportunistic_ui_update
Apr 25, 2026
Merged

android: optimistic UI for task complete and skip#308
dkhalife merged 2 commits intomainfrom
opportunistic_ui_update

Conversation

@dkhalife
Copy link
Copy Markdown
Owner

Summary

Tasks now disappear from the active list immediately when completed or skipped, instead of waiting for server sync.

Changes

  • LocalState.kt — Added PENDING_COMPLETE and PENDING_SKIP local states
  • TaskDao.kt — observeTasks() filters out PENDING_COMPLETE/PENDING_SKIP; remapId() preserves non-PENDING_CREATE states
  • TaskRepository.kt — enqueueTaskOp() accepts operation-specific localState; removed unused TaskWithSyncState/taskStates
  • TaskWizardApi.kt — completeTask/skipTask/uncompleteTask return Response (matching actual server response)
  • SyncCoordinator.kt — On success: upserts returned task if recurring, deletes if not. On failure: reverts state so task reappears
  • TaskListViewModel.kt — Removed unused pendingSyncTaskIds and dead imports

Edge Cases Handled

  • Failed sync: Task reappears in active list (reverted to PENDING_UPDATE), outbox retries on next sync
  • Offline: Task hidden immediately; processes when connectivity returns
  • Create-then-complete before sync: PENDING_CREATE preserved so CREATE processes first; remapId() preserves PENDING_COMPLETE state
  • Recurring tasks: Server returns updated task with new nextDueDate; upserted as SYNCED — no flicker

Tasks now disappear from the active list immediately when completed
or skipped, instead of waiting for server sync. The implementation
adds PENDING_COMPLETE and PENDING_SKIP local states that the Room
DAO query filters out, mirroring the existing PENDING_DELETE pattern.

The SyncCoordinator now parses the server response (which was already
returned but ignored) to handle recurring tasks correctly: if the
server returns a task with a next due date, it is upserted as SYNCED;
otherwise the task is deleted from Room. On sync failure, the local
state reverts to PENDING_UPDATE so the task reappears with a pending
indicator rather than vanishing permanently.

Also fixes an edge case where remapId (after CREATE sync) could
overwrite a subsequent PENDING_COMPLETE/PENDING_SKIP state, and
removes the unused pendingSyncTaskIds/TaskWithSyncState dead code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 25, 2026 16:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Android client’s local/offline sync flow to support optimistic UI when completing or skipping tasks: tasks are removed from the active list immediately, then later either finalized (delete/upsert) when the server confirms or reverted back into view on failure.

Changes:

  • Added PENDING_COMPLETE / PENDING_SKIP local states and filtered them out from the active task observation query.
  • Updated outbox sync handling for COMPLETE/SKIP/UNCOMPLETE to use the server-returned TaskResponse and to delete vs upsert based on recurrence.
  • Removed unused task sync state plumbing from the ViewModel/Repository.
Show a summary per file
File Description
android/app/src/main/java/com/dkhalife/tasks/viewmodel/TaskListViewModel.kt Removes unused pending-sync task state flow and related imports.
android/app/src/main/java/com/dkhalife/tasks/repo/TaskRepository.kt Adds operation-specific local state for enqueueing COMPLETE/SKIP and removes unused sync-state wrapper model.
android/app/src/main/java/com/dkhalife/tasks/data/sync/SyncCoordinator.kt Applies server-returned task updates on COMPLETE/SKIP/UNCOMPLETE and reverts local state on HTTP failure.
android/app/src/main/java/com/dkhalife/tasks/data/db/LocalState.kt Introduces PENDING_COMPLETE and PENDING_SKIP.
android/app/src/main/java/com/dkhalife/tasks/data/db/dao/TaskDao.kt Filters out pending-complete/skip tasks from observeTasks() and preserves non-create states during ID remap.
android/app/src/main/java/com/dkhalife/tasks/api/TaskWizardApi.kt Aligns task action endpoints to return Response<TaskResponse> (matching backend JSON shape).

Copilot's findings

  • Files reviewed: 6/6 changed files
  • Comments generated: 1

Comment thread android/app/src/main/java/com/dkhalife/tasks/repo/TaskRepository.kt Outdated
Only preserve PENDING_CREATE for coalescing operations (UPDATE) that
fold into the CREATE payload. COMPLETE and SKIP now override to their
specific states so the task disappears optimistically even in the
create-then-complete/skip flow. The CREATE handler uses getTaskById()
which is unfiltered, so it still builds the payload correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dkhalife
Copy link
Copy Markdown
Owner Author

Addressed in 42e7a1a — good catch. The fix now only preserves PENDING_CREATE for coalescing operations (i.e., when localState == PENDING_UPDATE), which covers UPDATE and UNCOMPLETE. COMPLETE/SKIP now override to their specific states so the task disappears optimistically even in the create-then-complete/skip flow. The CREATE handler uses getTaskById() (no state filter), so it still finds the task to build the payload. After CREATE succeeds, remapId() preserves the PENDING_COMPLETE/PENDING_SKIP state across the ID remap.

@dkhalife dkhalife merged commit 783b5ff into main Apr 25, 2026
6 checks passed
@dkhalife dkhalife deleted the opportunistic_ui_update branch April 25, 2026 17:01
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.

2 participants