Preventing spam of contradicting requests in your Android App
Preventing spam of contradicting requests in your Android App
Much frequently the pattern of 2 actions that contradict each other and also can be performed multiple times in a short time span is encountered on any app exposed to the public.
A classic example is the like button ping pong as I like to call it. Simple log into ex. Facebook and spam the like button, liking and disliking the same thing over and over again. Assuming that each click corresponds to a HTTP request, this is a huge waste of resources and can effectively cause your services to slow down significantly if scaled.
Thus, a mechanism is required to defend against this ‘edge’ case.
We will use 2 things to achieve such behavior:
- A single source of truth that supports LiveData
- A Linked List wrapper
The Single Source of Truth
The reason for this one is simple; I don’t want to care about stuff that happen on the background, on my ViewModel and on the Activity/Fragment. Just respect and observe your LiveData instances. You can use Room for that if you like; I’ve built an in-memory repository that provides LiveData because much of the data should not be cached.
Sometimes, when you don’t need the added complexity you may be able to get away by cramming everything into your ViewModel. Be careful and architect these steps properly because it’s hard to debug multiple executors running at once.
The Lean Linked List
Did you know that a Queue can be implemented via a Linked List? If you did not, you know now. Here are some visualizations of the problem before presenting the source code that does all this book-keeping.
Each item is of a specific ContradictableType. As the word implies, it can contradict with other instances of the same type, the same way a let’s say LikeRequest can contradict a DislikeRequest. It can also be deemed equivalent to another ContradictableType.
A like request aimed towards a resource with some id ‘x’ is equivalent to a like request aimed towards the same resource with the id ‘x’. Therefore, such requests have no point in being contained in the same Queue.
The first 2, cancel out each other and the second of the second 2 gets thrown away.
The Pedestal
Somehow, we’ve got to keep track of the requests that are in progress. Too much work is required to keep this outside of the Linked List, so we will just promote the first element (head) of the list. We will put it on a pedestal: it will be excluded by such elimination operations and will only be affected if removed by the poll() function.
Here’s the simple source code. The eliminations are only done while inserting. In the worse case scenario, the whole linked list is going to be traversed because no matching ContradictableTypes are found, so this would be O(n) worse case complexity.
Actually Using This
There are many elegant ways of using this kind of data structure. My preferred one would be combining this with a Hash Map in order to have the maximum performance and concurrency gain.
The hash map is going to have a ‘id’ string as a key. This best fits the target resource id you aim at. The value is going to correspond to a LeanLinkedList. For the like-dislike case, they have been modeled as boolean-backed contradictable types. Simple: true for like and false for dislike.
There are 2 parts of our worker function: The first one is protected with a synchronized keyword in order to protect us from concurrent modifications on the lists. The second part runs asynchronously (preferably on a random executor from your pool of network executors) and calls back the same function, running in request chain mode. That means that we are just going to remove the item on the pedestal and check if any more are left in the Queue to process.
Here’s the full pseudo-snippet. Notice that I am using a way of handling request from Retrofit described here, in detail.