fix(filtered-deck): restore state on recreation (#21463) by GuTS805 · Pull Request #21465 · ankidroid/Anki-Android · GitHub
Skip to content

fix(filtered-deck): restore state on recreation (#21463) - #21465

Merged
criticalAY merged 1 commit into
ankidroid:mainfrom
GuTS805:fix/filtered-deck-options-restore-21463
Aug 16, 2026
Merged

fix(filtered-deck): restore state on recreation (#21463)#21465
criticalAY merged 1 commit into
ankidroid:mainfrom
GuTS805:fix/filtered-deck-options-restore-21463

Conversation

@GuTS805

@GuTS805 GuTS805 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

this fixes the 3 things from the issue - after the screen gets recreated the duplicate name check
and the discard changes dialog both stop working, plus renaming a filtered deck back to its own
name was getting flagged as duplicate.

Purpose / Description

the init block in FilteredDeckOptionsViewModel was returning early whenever there was a previous
state saved:

val previousState = savedStateHandle.get<FilteredDeckOptions>(ARG_DATA)
if (previousState != null) {
    state.update { previousState }
    return@launch
}

problem is two things below that return never ran. decksNames stayed as emptyList() so the
decksNames.contains(name) check inside onDeckNameChange could never be true, and initialState
stayed null so wasStateModified() hit val initial = initialState ?: return false and always
returned false. so after a restore you could type a name that already exist and get no error at
all (Build stays enabled, you just get a raw backend exception when you press it), and pressing
back would throw away all your edits without asking anything.

the third one is unrelated to restore - safeGetDecksNames() uses includeFiltered = true so the
list also has the deck you are editing right now in it. so if you rename an existing filtered deck
and then put the original name back it says "already exists" and disables Rebuild.

Approach

moved decksNames = withCol { safeGetDecksNames() } above the early return since both paths need
it. for initialState i couldnt just read it back from ARG_DATA because that key holds the edited
state, not the original one, so its saved under its own key (ARG_INITIAL_DATA) and read back on
the restore path.

for the third one i used the existing title field - its already documented as the name from when
the deck was first loaded and it doesnt change for the lifetime of the screen, so its exactly what
was needed. only applied it when id != null so it doesnt affect creating new decks.

How Has This Been Tested?

added 3 tests to FilteredDeckOptionsViewModelTest, all of them failed before the fix:

name validation still works after the screen is restored -> expected but was
changed status still works after the screen is restored -> Expected value to be true
reverting to the deck's own name is not a duplicate -> expected but was

for simulating the restore theres a small withRestoredViewModel helper - it builds a view model,
throws it away and builds a second one over the same SavedStateHandle, which is basically what
happens when the VM dies but the handle survives.

also did mutation testing to make sure none of the 3 changes are redundant:

  • removed just the initialState restore -> only test 2 failed
  • removed the decksNames move + the own name check -> only tests 1 and 3 failed

whole com.ichi2.anki.filtered package is green after (17 tests), ktlint passes.

Checklist

  • You have a descriptive commit message with a short title (first line, max 50 chars).
  • You have commented your code, particularly in hard-to-understand areas
  • You have performed a self-review of your own code
  • UI changes: include screenshots of all affected screens (in particular showing any new or changed strings)
  • UI Changes: You have tested your change using the Google Accessibility Scanner

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

@david-allison david-allison added Needs Second Approval Has one approval, one more approval to merge squash-merge A squash & force push is required. The PR author may do this to speed up the merge process. and removed Needs Review labels Aug 11, 2026
@GuTS805
GuTS805 requested a review from david-allison August 12, 2026 01:31

@david-allison david-allison left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!

No need to re-request a review unless you feel it's required.

It partially removes the 'approved' in the GitHub UI.

@GuTS805

GuTS805 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Oh! sorry. I tapped it by mistake..

@criticalAY criticalAY left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM! thanks

@criticalAY criticalAY added Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) and removed Needs Second Approval Has one approval, one more approval to merge labels Aug 16, 2026
Three related defects in FilteredDeckOptionsViewModel:

The init block returned early when a previous state was found, skipping both
decksNames and initialState. After the view model was recreated (process death
or a system-initiated restore) decksNames stayed empty, so duplicate name
validation never fired, and initialState stayed null, so wasStateModified()
always returned false and the discard-changes dialog never showed. decksNames
is now loaded on both paths and initialState is persisted under its own key.

Separately, safeGetDecksNames() includes filtered decks, so renaming an
existing filtered deck and reverting to its original name was flagged as a
duplicate. The deck's own name is now excluded when editing.

Verified by mutation testing that each change is needed independently.

Fixes ankidroid#21463
@criticalAY
criticalAY force-pushed the fix/filtered-deck-options-restore-21463 branch from 7073928 to a59b388 Compare August 16, 2026 02:25
@criticalAY criticalAY removed the squash-merge A squash & force push is required. The PR author may do this to speed up the merge process. label Aug 16, 2026
@criticalAY

Copy link
Copy Markdown
Contributor
  • rebased, thanks!

@criticalAY
criticalAY enabled auto-merge August 16, 2026 02:25
@criticalAY
criticalAY added this pull request to the merge queue Aug 16, 2026
@GuTS805

GuTS805 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor Author

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 16, 2026
@criticalAY
criticalAY added this pull request to the merge queue Aug 16, 2026
Merged via the queue into ankidroid:main with commit 0146aff Aug 16, 2026
21 checks passed
@github-actions github-actions Bot added this to the 2.25 release milestone Aug 16, 2026
@github-actions github-actions Bot removed the Pending Merge Things with approval that are waiting future merge (e.g. targets a future release, CI wait, etc) label Aug 16, 2026
@GuTS805
GuTS805 deleted the fix/filtered-deck-options-restore-21463 branch August 16, 2026 03:48
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.

Filtered deck options: name validation and discard-changes dialog stop working after the screen is restored

3 participants