Skip to content

[Multiple-Profile]feat: allow profile deletion#20658

Open
criticalAY wants to merge 1 commit intoankidroid:mainfrom
criticalAY:mp/del-profiles
Open

[Multiple-Profile]feat: allow profile deletion#20658
criticalAY wants to merge 1 commit intoankidroid:mainfrom
criticalAY:mp/del-profiles

Conversation

@criticalAY
Copy link
Copy Markdown
Contributor

Purpose / Description

Allow profile deletion -> Adding a method in PrpfileManager to allow deletion or profiles except the active

Fixes

Approach

See commit

How Has This Been Tested?

Unit test

Learning (optional, can help others)

NA

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

@david-allison
Copy link
Copy Markdown
Member

Why is this a bulk operation?

@criticalAY
Copy link
Copy Markdown
Contributor Author

criticalAY commented Apr 5, 2026

What if user wants to delete 3-4 profiles in one go?

@david-allison
Copy link
Copy Markdown
Member

I doubt that's a realistic use case, and upstream doesn't support it. (Maybe when we have analytics back 😉).

I might be one of the few people where this would make sense (I might have 50 profiles). At this point, I don't know what each profile contains, so I'd still want to go one-by-one

@criticalAY
Copy link
Copy Markdown
Contributor Author

I actually did it for one profile at a time but I am okay with reverting to my original idea.

@david-allison
Copy link
Copy Markdown
Member

For what it's worth, I disabled multi-delete for tags - mixing filtering and deleting felt dangerous from a UI perspective

As deleting a profile is so risky, you might want explicit confirmation (type the profile name to delete it). Does this delete backups?

@criticalAY

This comment was marked as resolved.

@david-allison

This comment was marked as off-topic.

@criticalAY
Copy link
Copy Markdown
Contributor Author

We can do that, but is that related to this PR?

Copy link
Copy Markdown
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.

Much better!

"Cannot delete the currently active profile (${profileId.value}). Switch first."
}

profileRegistry.removeProfile(profileId)
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.

Think about the ordering here. Assume the user can kill the operation at any time. Is it better to have the files left on disk?

Comment thread AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt Outdated
Comment thread AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt Outdated
Comment thread AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt Outdated
@david-allison
Copy link
Copy Markdown
Member

I think a doc in /docs/AnkiDroid would be useful, just with the directory structure which was agreed upon (and the names of the concepts: profile directory etc...)

This would be a useful reference now to be sure all directories are handled and deleted

Comment thread AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt Outdated
Copy link
Copy Markdown
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.

My comment with a doc on the profile data/directory structure stands, it is ideal to have a single source of truth for the directories under a profile, and the global directories, so we can determine what should be deleted when a profile is deleted, to ensure there's no dangling files/directories

Comment on lines +236 to +242
* Scope of deletion is limited to the profile's private-storage
* directory (under `/storage/emulated/0/Android/data/com.ichi2.anki/files/...`):
* the collection database and any files the app writes inside that
* directory. Backups live in a separate, user-accessible location
* (`/storage/emulated/0/AnkiDroid/backups`) and are **not** touched
* here — their lifecycle is managed by the collection-folder guard
* elsewhere in the app. Deletion is irreversible.
Copy link
Copy Markdown
Member

@david-allison david-allison Apr 15, 2026

Choose a reason for hiding this comment

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

Backups live in a separate, user-accessible location (/storage/emulated/0/AnkiDroid/backups)

No they don't. They're under /storage/emulated/0/Android/data/com.ichi2.anki/files/ on a Google Play build

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ahh thanks, i found a bug thanks to this comment, default profile would have wiped all profiles, I have fixed that now added a test

Comment thread AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt Outdated
Comment thread AnkiDroid/src/main/java/com/ichi2/anki/multiprofile/ProfileManager.kt Outdated
Comment on lines +241 to +242
* BACKUPS & PRIVACY:
* On Google Play builds, backups are stored within the app's internal scoped storage
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.

On all builds. Backups live in the profile-specific directory

/**
* Permanently deletes a profile, removing its on-disk data and registry entry.
*
* SCOPE OF DELETION:
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.

These headers are unusual, no need for them

Introduce deleteProfile() for removing a single user profile.
Deletion removes both the registry entry (SharedPreferences) and
the on-disk data directory. The only guard is: the currently active
profile cannot be deleted, all other profiles including default
are deletable.
Comment on lines +260 to +269
if (appDataRoot != null) {
val sharedPrefsDir = File(appDataRoot, "shared_prefs")
if (sharedPrefsDir.exists() && sharedPrefsDir.isDirectory) {
val prefix = "profile_${profileId.value}_"
sharedPrefsDir.listFiles()?.forEach { file ->
if (file.name.startsWith(prefix)) {
file.delete()
}
}
}
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.

extract to a method - allows for early returns to simplify logic

* - External Storage (/storage/emulated/0/Android/data/.../files): Deletes the
* AnkiDroid collection directory, media, and backups for this profile.
*/
fun deleteProfile(profileId: ProfileId) {
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.

If a user can select an arbitrary directory: do we want to be more careful?

Comment on lines +273 to +281
val externalFilesDir = appContext.getExternalFilesDir(null)
if (externalFilesDir != null) {
val collectionFolderName = if (profileId.isDefault()) "AnkiDroid" else profileId.value
val collectionDir = File(externalFilesDir, collectionFolderName)

if (collectionDir.exists()) {
collectionDir.deleteRecursively()
}
}
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.

This is the first time AnkiDroid allows deleting a folder like this.

What if a user puts AnkiDroid in /Pictures/ - we likely want to selectively delete the known AnkiDroid files, rather than everything

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants