Common Dead Buttons in Salon Booking Apps: Causes and Fixes

All these issues are deterministic (they happen under the same state) but can be hidden by the sheer number of UI states a salon‑booking app supports (multiple services, staff, locations, discounts).

April 24, 2026 · 7 min read · Common Issues

1. What causes dead buttons in salon‑booking apps

Root causeWhy it shows up in a booking flowTypical symptom
Incorrect view‑bindingAndroid developers often use findViewById or view‑binding generators. A typo or stale binding after a fragment transaction leaves the OnClickListener attached to a view that is no longer in the hierarchy.Button looks normal but click events never reach the handler.
Conditional UI inflationMany screens (e.g., “Select Service”, “Add‑ons”) are built from JSON received from the server. If the JSON schema changes and the code skips inflating a button, the placeholder stays visible but is not wired.Button appears only for some locations or service categories.
Overlapping transparent viewsA ViewGroup (often a loading spinner or a debug overlay) is set android:visibility="invisible" instead of gone. The invisible view still consumes touch events, making the button underneath unresponsive.Works on a fresh install but fails after a long session when the overlay is never removed.
Throttle / debounce logic errorsTo prevent double‑booking, developers wrap button clicks in a debounce that disables the view for a fixed time. If the timer is never cleared (e.g., due to a crash in the callback), the button stays disabled forever.First tap works, subsequent taps are ignored even after the UI updates.
Incorrect navigation graph / deep‑link handlingIn Jetpack Navigation, an action ID may be mis‑typed or point to a fragment that does not exist. The click handler fires, but the navigation fails silently, leaving the UI unchanged.User thinks the button did nothing; no error is shown.
Web‑view bridge failuresHybrid apps embed a WebView for the booking calendar. The JavaScript bridge (addJavascriptInterface) may be stripped in release builds, so the native callback that enables the “Confirm” button never runs.Button is enabled visually but click events are lost in the bridge.
Accessibility‑related focus handlingWhen a button is marked android:importantForAccessibility="no" to hide it from TalkBack, some screen‑readers also block the click event for sighted users on certain devices.Only users with accessibility services experience the dead button.

All these issues are deterministic (they happen under the same state) but can be hidden by the sheer number of UI states a salon‑booking app supports (multiple services, staff, locations, discounts).

---

2. Real‑world impact

The cost is not only monetary; lost bookings also erode brand trust, making it harder to acquire new customers through word‑of‑mouth or referral programs.

---

3. 5–7 concrete examples of dead buttons in salon‑booking apps

  1. “Choose Staff” button does not respond after selecting a service – The service selection triggers a fragment replacement, but the new fragment reuses the old binding object, leaving the button without a listener.
  2. “Apply Promo Code” button stuck in disabled state – The debounce logic disables the button for 10 seconds after any API call; a network timeout never clears the timer, leaving the button forever disabled.
  3. “Proceed to Payment” button invisible to TalkBack – The button’s contentDescription is set correctly, but importantForAccessibility="no" was added to hide a decorative label, unintentionally blocking the click for all users on Android 12+.
  4. Web‑view “Select Time Slot” button dead on iOS – The native‑to‑JS bridge is removed by the ProGuard rule -keepclassmembers class * { @android.webkit.JavascriptInterface ; }. The JavaScript that enables the button never runs.
  5. “Add to Favorites” star icon unresponsive after scrolling – A transparent View used as a scroll sentinel (android:background="@android:color/transparent") sits on top of the star after the list is recycled, swallowing touches.
  6. “Cancel Appointment” button fails after a deep link – The deep link opens the “Appointment Details” screen via a navigation action that omits the cancelButton view ID in the generated Safe Args class.
  7. “Book Now” button works on fresh install but not after a crash – Crash recovery restores the UI state from a saved bundle but forgets to re‑attach the click listener that was set in onCreateView.

Each case is reproducible with a specific sequence of actions, which makes automated detection feasible.

---

4. How to detect dead buttons

4.1 Automated UI exploration (SUSA)

4.2 Instrumented testing

4.3 Static analysis

ToolWhat to look for
Lint / DetektUnused view bindings, setOnClickListener inside if (false) blocks, @JavascriptInterface missing in release builds.
Accessibility scannerButtons marked importantForAccessibility="no" while still visible.
OWASP Dependency‑CheckProGuard rules that strip bridge methods (security‑related but also UI‑breaking).

4.4 Manual sanity checks

---

5. How to fix each example (code‑level guidance)

  1. Fragment replacement binding
  2. 
       // Before
       private var _binding: FragmentChooseStaffBinding? = null
       private val binding get() = _binding!!
    
       override fun onCreateView(...) = FragmentChooseStaffBinding.inflate(inflater, container, false).also {
           _binding = it
       }.root
    
       // Fix: clear old binding on view destruction
       override fun onDestroyView() {
           super.onDestroyView()
           _binding = null   // prevents reuse of stale listeners
       }
    
  3. Debounce timer not cleared
  4. 
       // Replace fixed‑delay handler with coroutine timeout
       private var debounceJob: Job? = null
    
       fun onPromoApplyClicked() {
           debounceJob?.cancel()
           debounceJob = lifecycleScope.launch {
               button.isEnabled = false
               try {
                   applyPromo()
               } finally {
                   button.isEnabled = true
               }
           }
       }
    
  5. Accessibility flag
  6. 
       <!-- Remove the flag; keep the decorative label separate -->
       <Button
           android:id="@+id/btnConfirm"
           android:contentDescription="@string/confirm_booking"
           android:importantForAccessibility="yes" />
    
  7. ProGuard bridge rule
  8. 
       -keepclassmembers class * {
           @android.webkit.JavascriptInterface <methods>;
       }
       -keepclassmembers class com.yourapp.WebBridge {
           public *;
       }
    
  9. Transparent overlay stealing touches
  10. 
       // In RecyclerView adapter
       holder.itemView.setOnTouchListener { v, event ->
           // Only consume if the overlay is visible
           if (overlay.visibility == View.VISIBLE) true else false
       }
    
  11. Deep‑link navigation safe args
  12. 
       // nav_graph.xml
       <action
           android:id="@+id/action_appointmentDetails_to_cancel"
           app:destination="@id/cancelFragment"
           app:popUpTo="@id/appointmentDetails"
           app:popUpToInclusive="true" />
       // Ensure the generated CancelFragmentArgs contains cancelButtonId
    
  13. Crash recovery listener re‑attachment
  14. 
       override fun onViewStateRestored(savedInstanceState: Bundle?) {
           super.onViewStateRestored(savedInstanceState)
           // Re‑bind listeners after state restoration
           binding.btnBookNow.setOnClickListener { bookNow() }
       }
    

All fixes are idempotent – they do not alter the visual layout, only the event wiring, making them safe to ship in a hot‑fix.

---

6. Prevention: catching dead buttons before release

Prevention stepHow to implementSUSA integration point
Persona‑driven UI fuzzingInclude the 10 SUSA personas in every CI build. The “Curious” persona clicks every element; the “Impatient” persona taps twice quickly, exposing debounce bugs.Add a GitHub Actions step: susatest-agent run --apk app-debug.apk --persona all.
Regression script versioningStore the auto‑generated Appium & Playwright scripts in tests/auto/. Treat them as first‑class test assets; any change in UI triggers a diff that must be reviewed.SUSA outputs JUnit XML; feed it to the CI test reporter for immediate failure on new dead‑button detections.
Screen‑level coverage thresholdsDefine a minimum element‑coverage per screen (e.g., 95 %). SUSA’s coverage analytics list untapped elements; fail the build if the list is non‑empty for critical screens.susatest-agent coverage --threshold 95.
WCAG 2.1 AA checksRun SUSA’s accessibility persona suite. Buttons hidden from assistive tech will be reported as violations.Fail the pipeline on any WCAG error with severity “critical”.
Static rule set for click listenersAdd a custom Detekt rule that flags any View without an setOnClickListener or android:onClick attribute when android:clickable="true".Run Detekt as part of ./gradlew lintDetekt.
Cross‑session learning validationEnable SUSA’s cross‑session learning flag; after each test run, the agent updates a model that predicts “unresponsive element”. Review the model’s “high‑risk” predictions before merging.susatest-agent learn --persist.
Release‑candidate smoke testDeploy a staged build to Firebase App Distribution; run SUSA’s CLI against the distribution URL to verify the live environment (including remote config that may hide buttons).susatest-agent run --url https://app-distribution.firebase.com/....

By embedding these checks into the pull‑request pipeline, developers receive immediate feedback—often before the code is merged—eliminating the lag between a dead button appearing in production and a hot‑fix being released.

---

Bottom line

Dead buttons in salon‑booking apps are usually a symptom of event‑binding mismatches, overlapping views, or environment‑specific bridge failures. Their impact on user satisfaction and revenue is measurable, and the cost of fixing them after release dwarfs the modest investment needed to automate detection with SUSA, generate reliable regression scripts, and enforce persona‑based UI testing in CI. Adopt the detection‑fix‑prevent loop outlined above, and you’ll keep the “Book Now” button reliably tappable for every client—whether they’re a busy professional, an elderly user, or a teen on a tight schedule.

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.

Try SUSA Free