Hiding an app from the drawer deletes the entire home screen folder containing it
Maintainers usually reply within 3 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 70/100
Research direction
Start with MainActivity.kt, reading refreshLaunchers(), getAllAppLaunchers(), and hideIcon() to understand how hidden apps are distinguished from uninstalled apps. Then inspect HomeScreenGridItemsDao.kt and its deleteByPackageName() behavior. Done means hiding an app no longer removes its folder or the folder's other contents; verify by following the reproduction steps in the issue.
Written by the indexing model from the issue text.
Description
Checklist
- I can reproduce the bug with the latest version given here.
- I made sure that there are no existing issues - open or closed - to which I could contribute my information.
- I made sure that there are no existing discussions - open or closed - to which I could contribute my information.
- I have read the FAQs inside the app (Menu -> About -> FAQs) and my problem isn't listed.
- I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise.
- This issue contains only one bug.
- I have read and understood the contribution guidelines.
Affected app version
1.10.0 (Latest)
Affected Android/Custom ROM version
Android 13
Affected device model
Xiaomi Redmi Note 10 Pro
How did you install the app?
Google Play Store
Steps to reproduce the bug
- Open Fossify Launcher and place 2 or more app icons on the home screen
- Drag one app icon onto another to create a folder, then drag the remaining app(s) into the same folder — the folder should now contain multiple apps.
- Open the App Drawer (swipe up).
- Long-press on one of the apps that is inside the folder (e.g. Music) and select "Hide".
- Return to the home screen.
Expected behavior
- The hidden app should be removed from the folder.
- The remaining apps in the folder should stay in the folder.
- The folder should remain visible on the home screen with its remaining contents intact.
Actual behavior
- The entire folder disappears from the home screen, including all other apps that were inside it.
- All apps that were in the folder are completely removed from the home screen.
- The hidden app correctly appears in Settings → "Manage hidden icons", but the collateral damage to the folder and its other contents is permanent.
Screenshots/Screen recordings
| 1. Apps on the home screen | 2. Apps grouped into a folder |
|---|---|
| (Screenshot showing Music, Gallery, File Manager icons side by side on the home screen) |
(Screenshot showing the 3 apps inside a single folder) |
| 3. Hiding one app from the drawer | 4. Folder has disappeared |
|---|---|
| (Screenshot showing the long-press context menu with "Hide" selected on one of the apps) |
(Screenshot showing the home screen where the folder previously was — now empty) |
Additional information
Root Cause Analysis
The Bug Chain
The issue is caused by the refreshLaunchers() method in MainActivity mistakenly treating a hidden app as an uninstalled app, which triggers a destructive database cleanup that cascade-deletes the folder and all its contents.
Here is the exact execution chain:
Step 1 — User hides the app
When the user selects "Hide" from the app drawer context menu, MainActivity.hideIcon() (MainActivity.kt:882–891) executes:
private fun hideIcon(item: HomeScreenGridItem) {
ensureBackgroundThread {
val hiddenIcon = HiddenIcon(null, item.packageName, item.activityName, item.title, null)
hiddenIconsDB.insert(hiddenIcon)
runOnUiThread {
binding.allAppsFragment.root.onIconHidden(item)
}
}
}
This method only:
- Inserts a
HiddenIconrecord into thehidden_iconsdatabase table. - Removes the app from the drawer UI (
AllAppsFragment.onIconHidden()).
It does NOT:
- Remove the app's icon from any home screen folder.
- Clean up or update
home_screen_grid_itemsfor the hidden app. - Update
IconCache.launchersto reflect the hiding.
Step 2 — refreshLaunchers() treats the hidden app as uninstalled
On onResume(), refreshLaunchers() (MainActivity.kt:596–619) runs:
private fun refreshLaunchers() {
val launchers = getAllAppLaunchers() // Filters out hidden apps!
// ...
IconCache.launchers.map { it.packageName }.forEach { packageName ->
if (!launchers.map { it.packageName }.contains(packageName)) {
launchersDB.deleteApp(packageName)
homeScreenGridItemsDB.deleteByPackageName(packageName) // DESTRUCTIVE!
}
}
IconCache.launchers = launchers
// ...
}
getAllAppLaunchers() (MainActivity.kt:1082–1131) queries installed apps and filters out hidden ones:
if (hiddenIcons.contains("$packageName/$activityName")) {
continue // Hidden app excluded from result
}
The comparison loop at line 601–605 then finds the hidden app's package in IconCache.launchers (old cache, still containing the hidden app) but NOT in the new filtered launchers list. It concludes the app has been uninstalled and calls homeScreenGridItemsDB.deleteByPackageName(packageName).
Step 3 — deleteByPackageName cascade-deletes the folder
HomeScreenGridItemsDao.deleteByPackageName() (HomeScreenGridItemsDao.kt:56–60):
@Transaction
fun deleteByPackageName(packageName: String) {
deleteItemByPackageName(packageName) // (1)
deleteItemsByParentPackageName(packageName) // (2)
}
Query (1) — deleteItemByPackageName:
DELETE FROM home_screen_grid_items WHERE package_name = :packageName
This deletes ALL grid items (icons, shortcuts, AND the folder itself) whose package_name matches.
Critical detail: When a folder is created by dragging App A onto App B, the folder entity inherits App B's
packageName(see HomeScreenGrid.kt:734:potentialParent.copy(type = ITEM_TYPE_FOLDER, ...)). So if the hidden app'spackageNamematches the folder'spackageName, the folder itself is deleted.
Query (2) — deleteItemsByParentPackageName:
DELETE FROM home_screen_grid_items
WHERE parent_id IN (
SELECT id FROM home_screen_grid_items WHERE package_name = :packageName
)
This deletes ALL children of any item (folder) whose packageName matches. Since the folder's packageName may match the hidden app, all remaining items in the folder are also deleted.
Step 4 — fetchGridItems() redraws without the folder
After the cleanup, fetchGridItems() reloads from the database. The folder and its children are gone from the DB, so they no longer appear on the home screen.
Summary
User hides app → hideIcon() only adds to hidden_icons DB
→ onResume() → refreshLaunchers()
→ getAllAppLaunchers() excludes hidden app
→ Loop compares old cache vs new list → "missing" package detected
→ deleteByPackageName() runs → deletes folder + all children from DB
→ fetchGridItems() → folder is gone from home screen
The fundamental mistake is that refreshLaunchers() cannot distinguish between:
- An app that was uninstalled (should clean up home screen items)
- An app that was hidden (should NOT touch home screen items)
Suggested Fix
The fix should address the core issue: refreshLaunchers() should not treat hidden apps as uninstalled apps. There are multiple approaches:
Approach 1: Exclude hidden packages from the cleanup comparison (Recommended)
In refreshLaunchers(), add hidden packages to the known packages set so they are not treated as uninstalled:
private fun refreshLaunchers() {
val launchers = getAllAppLaunchers()
binding.allAppsFragment.root.gotLaunchers(launchers)
binding.widgetsFragment.root.getAppWidgets()
val hiddenPackages = hiddenIconsDB.getHiddenIcons().map { it.packageName }.toSet()
val knownPackages = launchers.map { it.packageName }.toSet() + hiddenPackages
IconCache.launchers.map { it.packageName }.forEach { packageName ->
if (!knownPackages.contains(packageName)) {
launchersDB.deleteApp(packageName)
homeScreenGridItemsDB.deleteByPackageName(packageName)
}
}
IconCache.launchers = launchers
// ...
}
Approach 2: Also remove the hidden icon from the folder in hideIcon()
In addition to Approach 1, hideIcon() should also handle removing the app from any home screen folder:
private fun hideIcon(item: HomeScreenGridItem) {
ensureBackgroundThread {
val hiddenIcon = HiddenIcon(null, item.packageName, item.activityName, item.title, null)
hiddenIconsDB.insert(hiddenIcon)
// Remove the hidden app's icon(s) from any home screen folders
val gridItems = homeScreenGridItemsDB.getAllItems()
gridItems.filter {
it.packageName == item.packageName
&& it.activityName == item.activityName
&& it.parentId != null
}.forEach { folderItem ->
homeScreenGridItemsDB.deleteItemById(folderItem.id!!)
homeScreenGridItemsDB.shiftFolderItems(
folderId = folderItem.parentId!!,
shiftFrom = folderItem.left,
shiftBy = -1
)
// Clean up empty folders
val remainingItems = homeScreenGridItemsDB.getFolderItems(folderItem.parentId!!)
if (remainingItems.isEmpty()) {
homeScreenGridItemsDB.deleteItemById(folderItem.parentId!!)
}
}
runOnUiThread {
binding.allAppsFragment.root.onIconHidden(item)
binding.homeScreenGrid.root.fetchGridItems()
}
}
}
Approach 3: Fix deleteByPackageName to be folder-aware
Modify deleteByPackageName so it does not delete folder entities or their children indiscriminately. Instead, it should only delete individual items and properly handle folder cleanup (remove from folder, shift indices, delete folder if empty).
Recommendation: Approach 1 is the minimal and safest fix. It directly addresses the root cause without refactoring the folder management logic. Approach 2 can be applied as an enhancement for a more complete UX where hiding an app also removes its icon from folders.
Related Files
| File | Relevance | Key Lines |
|---|---|---|
MainActivity.kt |
hideIcon() method, refreshLaunchers() method, onResume() lifecycle, getAllAppLaunchers() |
L269–289, L596–619, L882–891, L1082–1131 |
HomeScreenGridItemsDao.kt |
deleteByPackageName() cascade transaction, deleteItemByPackageName, deleteItemsByParentPackageName |
L44–48, L56–60 |
HomeScreenGrid.kt |
fetchGridItems(), folder creation logic (potentialParent.copy), removeItemFromHomeScreen(), HomeScreenFolder.getItems() |
L236–261, L290–351, L733–761, L1941–1980 |
AllAppsFragment.kt |
onIconHidden() drawer update, onAppLauncherLongPressed() |
L160–176, L221–246 |
Activity.kt |
handleGridItemPopupMenu() — "Hide" option visibility logic |
L106–107, L160 |
HiddenIcon.kt |
HiddenIcon data model |
L6–18 |
HomeScreenGridItem.kt |
Grid item entity — parentId, packageName, type, FOLDER_MAX_CAPACITY |
L12–41 |
- Dominant language
- Kotlin
- Stars
- 703
- Forks
- 85
- Avg merge
- 4d 39m
- Merged PRs (30d)
- 7
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from FossifyOrg/Launcher
-
bug needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
FossifyOrg/Launcher#453 ·
Maintainers usually reply within 3 days
-
bug needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
FossifyOrg/Launcher#447 ·
Maintainers usually reply within 3 days
-
bug needs triage
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
FossifyOrg/Launcher#446 ·
Maintainers usually reply within 3 days
-
feature request needs triage
Difficulty 3/5 1-2 days Newbie friendliness 40/100
FossifyOrg/Launcher#461 ·
Maintainers usually reply within 3 days
-
bug needs triage
Difficulty 4/5 3-5 days Newbie friendliness 48/100
FossifyOrg/Launcher#456 ·
Maintainers usually reply within 3 days
All issues in FossifyOrg/Launcher
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
ankidroid/Anki-Android#22408 ·
Maintainers usually reply within 1 day
-
enhancement
Difficulty 2/5 Under an hour Newbie friendliness 72/100
afarber/OpenMapView#22 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
nightscout/AndroidAPS#5245 ·
Maintainers usually reply within 1 day
-
Feature:Resolution
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
intellij-elixir/intellij-elixir#4396 ·
Maintainers usually reply within 1 day