A few weeks ago I was working on an Android sample connecting to Windows Azure Mobile Services to do different kinds of authentication. One of the features of that sample was the ability to ask the user to login if they made a request that required they be authenticated but the token they were using had expired. After they logged in again, the previous request would then be retried. Unfortunately, at the time it seemed like I would need to add something custom to the Mobile Services SDK for Android in order to track the previous request and then retry it. This resulted in the addition of quite a bit of code. After posting that sample and walking through the changes I made with the SDK team, we found a much easier way to accomplish the task without making any changes to the SDK: using a CountDownLatch.
What is a CountDownLatch?
A CountDownLatch is a synchronization aid that enables you to block a thread until the countdown reaches zero. In other words, you can pause execution of one thread until a second thread says “ok you can finish now”. How does this help us with the scenario above? Well, due to the fact that network requests can’t (and shouldn’t) be made on the main (UI) thread, you have to perform these requests on a separate thread. When we make the first request (with the expired token) the response is handled on a background thread. From this thread, if we want to make the user log in again, we need to open the login dialog, but we need to jump back to the UI thread to do that. When we get the response from the login dialog that they’ve successfully authenticated, we then need to update our request with the new token and rerun that request but on a background thread! That’s a lot of jumping back and forth so I’ve made this diagram in an attempt to simplify things:
So as you can see, this is a bit complicated. My previous solution was to keep a copy of the request that was made and, if we had to re-login the user, to alter that copied request and then reissue it. At the time I thought this was necessary because Mobile Services requests are wrapped up in the AsyncTask class and you can’t have the same task run twice. However, I was missing the fact that I could use the same service filter to reissue the request. That might not make sense right now but I’ll show you some code and it will clear things up for you quite a bit.
I’ve taken the code from the previous sample and removed the code not necessary for explaining how this works:
This ServiceFilter’s handleRequest method will be called prior to any request being made against my Mobile Service. It uses the nextServiceFilterCallback to call onNext which will process that request and call the onResponse method when it gets a result back. Inside that onResponse, we check for a 401 and if so, we create our CountDownLatch with an initial value of 1 (since we’re only waiting on one operation to complete). We then use the current activity to call runOnUiThread and ask the user to login again. When the onCompleted method of the login is called, if there wasn’t an exception, we can update our request object (left out for brevity) and call latch.coundDown(). This call basically says “if anyone called await() on this CountDownLatch, you can proceed now!” Right beneath the block we have to run code on the UI thread, the onResponse method continues with a call to latch.await(). So basically, we’re running some code on the UI thread and in the background thread we’re saying “let’s wait for that to be done”. Once the UI thread is done, we use the same nextServiceFilterCallback onNext method to process the request. Now we’ve got the same process we had before working but without needing to track the previous request, request type, table, etc. Much easier (though a bit to understand).
Using the CountDownLatch gives you an easy way to block a thread and wait for some action to be performed. This is very useful if you’re doing background processing of any sort. In this specific situation, it saves us from needing to keep track of quite a few variables so we can reprocess a request. I’ve updated the sample code for the authentication demo so it now includes this code instead of the old version that required SDK changes and you can read more about it here.