From f0f4e6d0dd6ac246d6dd1c5e0e60e238b1a8a554 Mon Sep 17 00:00:00 2001 From: Sumit Singh Date: Sat, 28 Feb 2026 02:37:37 +0530 Subject: [PATCH 1/3] fix: prevent duplicate startup sync on resume --- AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt index 90a655016d64..2ebb9d7ea647 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt @@ -804,6 +804,7 @@ open class DeckPicker : } is StartupResponse.Success -> { + viewModel.flowOfStartupResponse.value = null // Prevent duplicate startup on re-resume showStartupScreensAndDialogs(sharedPrefs(), 0) if (tryShowStudyOptionsPanel()) { From 508117ff36a0cb5807dc25bded4c0bd333d20152 Mon Sep 17 00:00:00 2001 From: Sumit Singh Date: Wed, 4 Mar 2026 22:11:37 +0530 Subject: [PATCH 2/3] consume startup response in ViewModel --- AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt | 4 ++-- .../com/ichi2/anki/deckpicker/DeckPickerViewModel.kt | 7 +++++++ .../com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt | 9 +++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt index 2ebb9d7ea647..2f633a8ea1fc 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt @@ -797,14 +797,14 @@ open class DeckPicker : Timber.d("onStartupResponse: %s", response) when (response) { is StartupResponse.RequestPermissions -> { - viewModel.flowOfStartupResponse.value = null // Prevent duplicate permission screen launches + viewModel.consumeStartupResponse() permissionScreenLauncher.launch( PermissionsActivity.getIntent(this, response.requiredPermissions), ) } is StartupResponse.Success -> { - viewModel.flowOfStartupResponse.value = null // Prevent duplicate startup on re-resume + viewModel.consumeStartupResponse() showStartupScreensAndDialogs(sharedPrefs(), 0) if (tryShowStudyOptionsPanel()) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt index a974ddde1962..ab599e705f61 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt @@ -79,6 +79,13 @@ class DeckPickerViewModel : OnErrorListener { val flowOfStartupResponse = MutableStateFlow(null) + /** + * Clears the current startup response so it is only handled once. + */ + fun consumeStartupResponse() { + flowOfStartupResponse.value = null + } + private val flowOfDeckDueTree = MutableStateFlow(null) /** The root of the tree displaying all decks */ diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt index ae5f0ee83bb1..a26e770ce37f 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt @@ -147,6 +147,15 @@ class DeckPickerViewModelTest : RobolectricTest() { } } + @Test + fun `consumeStartupResponse clears the current value`() { + runTest { + viewModel.flowOfStartupResponse.value = DeckPickerViewModel.StartupResponse.Success + viewModel.consumeStartupResponse() + assertThat("startup response is cleared after consumption", viewModel.flowOfStartupResponse.value, equalTo(null)) + } + } + /** * Creates a note with 3 cards, all empty * From 4a70409d430005d9fd3dcde12a57fea70f4d64d1 Mon Sep 17 00:00:00 2001 From: Sumit Singh Date: Thu, 5 Mar 2026 00:27:48 +0530 Subject: [PATCH 3/3] add activity test for startup response clear after handle --- AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt | 4 ++-- .../com/ichi2/anki/deckpicker/DeckPickerViewModel.kt | 7 ------- .../src/test/java/com/ichi2/anki/DeckPickerTest.kt | 11 +++++++++++ .../ichi2/anki/deckpicker/DeckPickerViewModelTest.kt | 9 --------- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt index 2f633a8ea1fc..2ebb9d7ea647 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/DeckPicker.kt @@ -797,14 +797,14 @@ open class DeckPicker : Timber.d("onStartupResponse: %s", response) when (response) { is StartupResponse.RequestPermissions -> { - viewModel.consumeStartupResponse() + viewModel.flowOfStartupResponse.value = null // Prevent duplicate permission screen launches permissionScreenLauncher.launch( PermissionsActivity.getIntent(this, response.requiredPermissions), ) } is StartupResponse.Success -> { - viewModel.consumeStartupResponse() + viewModel.flowOfStartupResponse.value = null // Prevent duplicate startup on re-resume showStartupScreensAndDialogs(sharedPrefs(), 0) if (tryShowStudyOptionsPanel()) { diff --git a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt index ab599e705f61..a974ddde1962 100644 --- a/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt +++ b/AnkiDroid/src/main/java/com/ichi2/anki/deckpicker/DeckPickerViewModel.kt @@ -79,13 +79,6 @@ class DeckPickerViewModel : OnErrorListener { val flowOfStartupResponse = MutableStateFlow(null) - /** - * Clears the current startup response so it is only handled once. - */ - fun consumeStartupResponse() { - flowOfStartupResponse.value = null - } - private val flowOfDeckDueTree = MutableStateFlow(null) /** The root of the tree displaying all decks */ diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt index 26913e2cc94e..882ff0b9c2d9 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/DeckPickerTest.kt @@ -661,6 +661,17 @@ class DeckPickerTest : RobolectricTest() { ) } + @Test + fun `startup response is cleared after handling so it does not re-run on resume`() = + deckPicker { + ShadowLooper.runUiThreadTasksIncludingDelayedTasks() + assertThat( + "startup response cleared after handling so it does not re-run on resume", + viewModel.flowOfStartupResponse.value, + nullValue(), + ) + } + /** * Emulates a null collection and a `BackendDbLockedException` * diff --git a/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt b/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt index a26e770ce37f..ae5f0ee83bb1 100644 --- a/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt +++ b/AnkiDroid/src/test/java/com/ichi2/anki/deckpicker/DeckPickerViewModelTest.kt @@ -147,15 +147,6 @@ class DeckPickerViewModelTest : RobolectricTest() { } } - @Test - fun `consumeStartupResponse clears the current value`() { - runTest { - viewModel.flowOfStartupResponse.value = DeckPickerViewModel.StartupResponse.Success - viewModel.consumeStartupResponse() - assertThat("startup response is cleared after consumption", viewModel.flowOfStartupResponse.value, equalTo(null)) - } - } - /** * Creates a note with 3 cards, all empty *