Skip to content

Hiding an app from the drawer deletes the entire home screen folder containing it #423

Description

@akbudakanl

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

  1. Open Fossify Launcher and place 2 or more app icons on the home screen
  2. 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.
  3. Open the App Drawer (swipe up).
  4. Long-press on one of the apps that is inside the folder (e.g. Music) and select "Hide".
  5. 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) ignoreImageMinify (Screenshot showing the 3 apps inside a single folder) ignoreImageMinify
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)
ignoreImageMinify
(Screenshot showing the home screen where the folder previously was — now empty)
ignoreImageMinify

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:

  1. Inserts a HiddenIcon record into the hidden_icons database table.
  2. 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_items for the hidden app.
  • Update IconCache.launchers to 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's packageName matches the folder's packageName, 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not workingneeds triageIssue is not yet ready for PR authors to take up

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions