Android—How to handle taps?

Karlo Vrbić
Azikus
Published in
5 min readJan 29, 2021

--

One of the most common problems we encounter, but rarely think about, is handling user’s taps on UI elements.

On Android, the Looper class—launched in the main/UI thread on the app’s start-up—provides a message queue for the UI thread, which is responsible for many things. One of them is handling the user’s gestures.

If the user, purposely or by mistake, taps more than once on a View with associated View.OnClickListener in a very small time interval, it’s possible that multiple tap events will be sent to the message queue, and subsequently, the OnClickListener.onClick(View) function will be invoked multiple times before the code inside the click listener has finished executing.

Another example would be invoking a long-running process on another thread and tapping again on the View while the operation (e.g. network call) is still being executed. The UI thread would receive another message indicating a tap event and the operation would be started for the second time.

This could be manifested in your app in several ways, including:

  • Making multiple API calls, unnecessarily consuming the user’s data plan, and potentially making unwanted transactions on the API
  • Launching multiple instances of the same Activity, unless regulated by launch modes
  • Trying to navigate to a Fragment using Navigation Component and receiving IllegalArgumentException: "navigation destination XXX is unknown to this NavController

Here we’d like to share a few solutions that help us resolve this while keeping code changes to a minimum.

Click vs. tap

Although this isn’t the focus of the article, all software engineers can agree that properly naming things is a prerequisite for good and clean code.

There is a famous saying attributed to the late Phil Karlton:

There are only two hard things in Computer Science: cache invalidation and naming things.

Android’s View class exposes a setOnClickListener function to handle tap gestures.
On iOS, UITapGestureRecognizer accomplishes the same thing.
Notice the difference in the naming.
As the vast majority of Android/iOS users use touch-screen devices and they perform taps rather than clicks, we’ve decided to use the word tap instead of click in our naming conventions on both Android and iOS projects.

Now, let’s fix this!

Tap Listener

Our goal is to have a simple, clean interface that enables reacting to tap gestures. Additionally, we want to be able to customize the look of the View depending on its state.
To do that, we define two classes: OnTapBehaviour and OnTapListener .

OnTapBehaviour is an interface that handles the state of the View after its tap has been enabled/disabled.
Some examples of implementations would be: DoNothingOnTapBehaviour, DisableViewOnTapBehaviour, ChangeBackgroundColourOnTapBehaviour etc.

All tap listeners extend the OnTapListener abstract class.
It’s constructed with the aforementioned OnTapBehaviour and a lambda function which is invoked when the View is tapped.

1. Asynchronous Listener

The code above is, in fact, a form of async listener. Once the onTap(View) function is invoked, the tap listener will be disabled and subsequent taps on the View won’t be propagated.

By defining a couple of extension functions, we can define a very developer-friendly API:

Now when we want to have our custom tap listener, we can simply use extension functions just like we would use setOnClickListener from the Android View API. However, unlike using View.OnClickListener , we would store a reference to the listener and enable the button when the operation started by the tap has finished.

You would use this solution if you wanted to disable the user’s interaction with the element until time-consuming operations have been executed, such as network calls, database access, heavy calculations, or similar.
This is how it looks in action:

Asynchronous listeners with different behaviors

2. Debounced Listener

For simpler usage, where we only want to prevent accidental double-taps and such, the solution is to reject the taps after the initial one, until the predefined time interval has passed after that tap.
If you’re familiar with the Reactive Programming Paradigm, this behavior is similar to the Debounce operator.
The listener will enable itself after the time interval has passed.

The implementation ofDebouncedOnTapListener is pretty straightforward:

The same as in the AsyncOnTapListener example, we create a couple of developer-friendly extensions:

The usage looks the same as using the View API:

GIF from our example app:

Debounced listeners with different behaviors

Summary

In the case of asynchronous listener, a reference to it is needed so we can enable it manually. This plays nice with the long-running tasks in which there is no benefit of running the same task before the first one has finished, like network calls, obtaining location from the device, and similar interactions with hardware.

The biggest advantage of the debounced listener is the simplicity of use. The disadvantage is that it doesn’t know if the operation that it started has finished. This makes it a perfect fit for use with buttons that trigger navigation, whether with Intent or NavController, or simple synchronous tasks that you want to prevent the users from abusing.

Both approaches solve the multiple tap issue present in the Android platform.
By applying this approach, you can mitigate the multiple taps issue, thus increasing the stability of your app, and gain more control over the user’s interaction with your app.

Sample app

We made a simple app to show you how we use it in a “real-life” scenario.
This app tracks reported Covid-19 numbers all over the world. You can search for the numbers by country/region/city using async and debounce listeners.
It also uses Kotlin Gradle DSL, Coroutines, KotlinX Serialization, Dagger Hilt and other cutting-edge libraries.

Karlo is a valuable member of our Android team.
At Azikus, we design and develop top notch mobile and web apps.
We are a bunch of experienced developers and designers who like to share knowledge, always staying up to date with latest and newest technologies.
To find out more about what we do, feel free to check our
website.

--

--