From 35d2d5f677939f2ebfea9448a3852e1bf822b528 Mon Sep 17 00:00:00 2001 From: xhon-pelushi Date: Thu, 13 Aug 2026 09:57:33 -0400 Subject: [PATCH] Debounce details screen navigation to prevent crash Rapidly tapping a package list item (e.g. a dependency shown on the details screen itself) queued fragment transactions faster than their postponed enter transitions could finish, leaving FragmentManager in an inconsistent state and crashing with: IllegalStateException: Fragment DetailsScreen{...} did not return a View from onCreateView() or this was called before onCreateView(). Ignore repeated navigation to the details screen within 500ms of the previous one so only one transition is ever in flight. Fixes #436 --- .../app/grapheneos/apps/ui/PackageListAdapter.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/app/grapheneos/apps/ui/PackageListAdapter.kt b/app/src/main/java/app/grapheneos/apps/ui/PackageListAdapter.kt index 357155071..24f395784 100644 --- a/app/src/main/java/app/grapheneos/apps/ui/PackageListAdapter.kt +++ b/app/src/main/java/app/grapheneos/apps/ui/PackageListAdapter.kt @@ -1,6 +1,7 @@ package app.grapheneos.apps.ui import android.annotation.SuppressLint +import android.os.SystemClock import android.view.ViewGroup import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat @@ -112,8 +113,21 @@ fun PackageListItemBinding.set(fragment: Fragment, pkgState: PackageState) { root.tag = pkgState } +// Debounces navigation to the details screen: without this, tapping a list item (e.g. a +// dependency shown within the details screen itself) repeatedly in quick succession queues up +// fragment transactions faster than their enter transitions can finish, which can crash +// FragmentManager (see https://github.com/GrapheneOS/AppStore/issues/436). +private const val DETAILS_NAVIGATION_DEBOUNCE_MS = 500L +private var lastDetailsNavigationTime = 0L + fun PackageListItemBinding.setOnClickListener(fragment: Fragment) { root.setOnClickListener { root -> + val now = SystemClock.elapsedRealtime() + if (now - lastDetailsNavigationTime < DETAILS_NAVIGATION_DEBOUNCE_MS) { + return@setOnClickListener + } + lastDetailsNavigationTime = now + val packageState = root.tag as PackageState val pkgName = packageState.pkgName fragment.findNavController().navigate(NavGraphDirections.actionToDetailsScreen(pkgName))