Hiding an app from the drawer deletes the entire home screen folder containing it
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 3/5
- 見積もり時間
- 1〜2日
- 初心者へのやさしさ
- 70/100
調査の方向性
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.
索引モデルが issue の本文から書いたものです。
説明
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 |
- 主要言語
- Kotlin
- スター
- 703
- フォーク
- 85
- 平均マージ
- 2日 22時間
- マージ済み PR(30日)
- 10
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
FossifyOrg/Launcher のほかの issue
-
bug needs triage
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
FossifyOrg/Launcher#453 ·
メンテナーはふだん 1 日以内に返信
-
bug needs triage
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
FossifyOrg/Launcher#447 ·
メンテナーはふだん 1 日以内に返信
-
bug needs triage
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
FossifyOrg/Launcher#446 ·
メンテナーはふだん 1 日以内に返信
-
feature request needs triage
難易度 3/5 1〜2日 初心者へのやさしさ 58/100
FossifyOrg/Launcher#465 ·
メンテナーはふだん 1 日以内に返信
-
feature request needs triage
難易度 3/5 1〜2日 初心者へのやさしさ 40/100
FossifyOrg/Launcher#461 ·
メンテナーはふだん 1 日以内に返信
FossifyOrg/Launcher の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 83/100
Kotlin/kotlin-lsp#288 ·
-
needs-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
chrisbanes/haze#1536 ·
メンテナーはふだん 1 日以内に返信
-
Cassiopeia drops the `präsentiert von` credit printed in the event body対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンafk-ok area:data-quality importer size:S
難易度 2/5 1〜3時間 初心者へのやさしさ 77/100
enorm-labs/event-junkie#3166 ·
メンテナーはふだん 1 日以内に返信
-
enhancement good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
rajpreetcodes/Skills_MCP_Server#25 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
com-lihaoyi/mill#7776 ·
メンテナーはふだん 1 日以内に返信