Skip to content

Fix database corruption dialog#19692

Merged
lukstbit merged 2 commits intoankidroid:mainfrom
Alex7D3:fix/database-corruption-dialog
Feb 10, 2026
Merged

Fix database corruption dialog#19692
lukstbit merged 2 commits intoankidroid:mainfrom
Alex7D3:fix/database-corruption-dialog

Conversation

@Alex7D3
Copy link
Contributor

@Alex7D3 Alex7D3 commented Dec 5, 2025

This avoids a SQLiteDatabaseCorruptException escaping and ensures the correct dialog is displayed rather than a generic error dialog

Purpose / Description

Accessing the collection with methods like CollectionManger::withCol or CollectionManager::getColUnsafe will attempt to open the database. When the file is corrupt this throws a SQLiteDatabaseCorruptException. In DeckPicker, withCol throws the error which is caught by a launchCatchingTask and a generic error dialog is displayed rather than the intended one.

Fixes

Approach

Rather than accessing the collection object directly, to avoid opening the database the collection file is accessed from CollectionManager, and BackupManager::repairCollection was changed slightly to accommodate this.

Note that the logic in repairCollection does not guarantee the dialog will always appear even if the file is still corrupt (in my testing at least). For example if return repairedFile.renameTo(colFile) succeeds and returns true, no dialog message will appear but the database could still be corrupt and prevent the decks from loading.

I am not sure what the course of action is here, maybe a stronger condition for repairCollection?

How Has This Been Tested?

I am not sure if there is a better way to do this, there probably is. I couldn't find an integration test that corrupts the database, just unit tests (if there isn't maybe this is something worth having).

I copied
Anki-Android-Backend/rsdroid-instrumented/src/androidTest/assets/initial_version_2_12_1_corrupt_regular.anki2
into
AnkiDroid/src/main/assets/.
This file is a corrupt collection used for a backend unit test. Then I modified DeckPicker::handleStartup temporarily for testing:

private fun handleStartup() {
    val context = AnkiDroidApp.instance

    val environment: AnkiDroidEnvironment =
        object : AnkiDroidEnvironment {
            private val folder = selectAnkiDroidFolder(context)

            override fun hasRequiredPermissions(): Boolean = folder.hasRequiredPermissions(context)

            override val requiredPermissions: PermissionSet
                get() = folder.permissionSet

            override fun initializeAnkiDroidFolder(): Boolean = CollectionHelper.isCurrentAnkiDroidDirAccessible(context)
        }
+   val dir = getCurrentAnkiDroidDirectory(context)
+   val targetDb = File(dir, "collection.anki2")
+   this.assets.open("initial_version_2_12_1_corrupt_regular.anki2").use { input ->
+       targetDb.outputStream().use { output ->
+           input.copyTo(output)
+       }
+   }
    viewModel.handleStartup(environment = environment)
}

and temporarily modified BackupManager::repairCollection to always return false to guarantee the dialog would appear during testing.

  • Automated test suite
  • Manually on Emulator
  • Pixel 6 Pro API 31
Screencast.from.12-05-2025.05.57.47.AM.webm

Learning (optional, can help others)

Describe the research stage

Links to blog posts, patterns, libraries or addons used to solve this problem

Checklist

Please, go through these checks before submitting the PR.

  • 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

Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

I feel this PR mixes two concepts:

  1. launchCatchingTask does not handle a database corruption error properly
  2. (maybe) repairCollection does not work if the database is corrupt

We should firstly fix the linked issue: showing the correct dialog via launchCatchingTask: anywhere in the app can throw due to a corrupt collection, and launchCatchingTask is our standard error handler which should handle this.

If repairCollection is broken, let's open a separate issue for it. This fix is likely correct, but I'd like to see a regression test here.

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Dec 6, 2025
@github-actions

This comment was marked as outdated.

@github-actions github-actions bot added the Stale label Dec 20, 2025
@Alex7D3
Copy link
Contributor Author

Alex7D3 commented Dec 21, 2025

Potential solution? Perhaps there is a cleaner way... but this works without making too many changes.
Also doesn't address repairing but we can deal with that in a separate issue after.

@github-actions github-actions bot removed the Stale label Dec 21, 2025
Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

Thanks! Do you want me to submit the issue with repairCollection, or do you want to submit a separate PR.

That fix looked good

Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

OK, this looks good. Needs:

  • automated test (ideally with a corrupt collection, rather than just throwing the exception)
  • correcting the catch block
  • setting databaseCorruptFlag

Ensures `launchCatchingTask` and `InitialActivity` handle `SQLiteDatabaseCorruptException` with correct
dialog
@Alex7D3 Alex7D3 force-pushed the fix/database-corruption-dialog branch 2 times, most recently from 19273c7 to 44949e5 Compare December 30, 2025 23:23
@Alex7D3
Copy link
Contributor Author

Alex7D3 commented Dec 30, 2025

Found out there was another area that needs to handle the exception in InitialActivity. This makes sure the dialog appears on app launch (startup failure handling in DeckPicker::onCreate). Changes in CoroutineHelpers is to handle everywhere else (such as after attempting a repair).

@david-allison david-allison added Needs Review and removed Needs Author Reply Waiting for a reply from the original author labels Jan 5, 2026
Copy link
Member

@david-allison david-allison left a comment

Choose a reason for hiding this comment

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

One query on the test, the rest looks good to go

You created a file, deleteOnExit() for normal handling, and deleting it manually in the teardown method seems appropriate

@david-allison david-allison added the Needs Author Reply Waiting for a reply from the original author label Jan 5, 2026
@Alex7D3 Alex7D3 force-pushed the fix/database-corruption-dialog branch from 44949e5 to 74accdc Compare January 6, 2026 05:57
@Alex7D3
Copy link
Contributor Author

Alex7D3 commented Jan 6, 2026

One query on the test, the rest looks good to go

You created a file, deleteOnExit() for normal handling, and deleting it manually in the teardown method seems appropriate

Maybe good to add mkdirs() as well

@Alex7D3 Alex7D3 force-pushed the fix/database-corruption-dialog branch from 74accdc to 3c7be78 Compare January 6, 2026 17:24
@Alex7D3 Alex7D3 requested a review from david-allison January 6, 2026 17:24
@david-allison david-allison added Needs Second Approval Has one approval, one more approval to merge and removed Needs Author Reply Waiting for a reply from the original author Needs Review labels Jan 7, 2026
Copy link
Member

@lukstbit lukstbit left a comment

Choose a reason for hiding this comment

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

Looks good, thanks!

@lukstbit lukstbit 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 Feb 10, 2026
@lukstbit lukstbit added this pull request to the merge queue Feb 10, 2026
Merged via the queue into ankidroid:main with commit 7946cc7 Feb 10, 2026
15 checks passed
@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 Feb 10, 2026
@github-actions github-actions bot added this to the 2.24 release milestone Feb 10, 2026
@Alex7D3
Copy link
Contributor Author

Alex7D3 commented Feb 12, 2026

Looks good, thanks!

Thank you!

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.

Database corruption no longer shows DIALOG_LOAD_FAILED: SqliteFailure; DatabaseCorrupt; database disk image is malformed

3 participants