Tuesday, 17 June 2014

Calling web services asynchronously in Android

This is something I had to do when first creating my PixelPin Android App. Depending on what web service client you use, you might find that you cannot call the web service from the main thread without causing an exception. I ended up using the Apache web service client, since it matched much more closely with a coding style I was familiar with (the Java one is really confusing!) but it is still not a good idea to call a web service from the main (UI) thread for the simple reason that the call will block, possibly for 10s of seconds in a bad network area and that would cause the UI to lock up which is obviously not good.

Below are some code extracts that describe the way in which I call the web service asynchronously using a simple generic class called android.os.AsyncTask, which does most of the heavy lifting to move the blocking code off of the main thread.

The calling Activity

So there will obviously be an activity that needs to call the web service, rather than calling this service directly and blocking, we will start the Async task (shown in a minute) and then finish. For instance, our Activity might call something like this:

CheckUserCanLoginTask task = new CheckUserCanLoginTask();
task.attach(LoginActivity.this);
task.execute(email, getDeviceId());

We will look at the code for the Task in a minute but there are a few things to point out. Firstly, the attach method is what I have used to allow the Task to call back into the Activity when it has finished. You could pass it in as one of the parameters to execute but I think this way is more clear. The execute() function can take a variable number of parameters but they must all be the same type and will be specified in the code for CheckUserCanLoginTask. In my case, String is usually the lowest common denominator so I tend to use String... for the params here. The actual params are not important, in this case they are simply the parameters I need for my task to work. You also don't have to pass any parameters in if they are not relevant.

The Async Task

The async class is generic and takes 3 type parameters, the first is the type of the variable length params list passed to the execute() function, the second is the type of the function that can be called to check on the progress of the task (if relevant, I don't tend to use this for the web service calls) and the 3rd type is the type of the result that is passed from the async function doInBackground to the 'finished' function, which is called onPostExecute. My code for this specific task looks like this:

package org.PixelPin.PixelPinMobile;

import android.os.AsyncTask;

public class CheckUserCanLoginTask extends AsyncTask<String,String,String> {
    
    private LoginActivity activity;
    
    public void attach(LoginActivity act)
    {
        activity = act;
    }
    
    @Override
    protected String doInBackground(String... params) {
        
        return PixelPinWebService.CheckUserCanLogin(params[0],params[1]);
    }
    
    @Override
    protected void onPostExecute(String result) {
        activity.CheckUserFinished(result);
    }

}


There isn't much that is complicated here but note that the doInBackground() function is asynchronous, it is NOT the same as the execute function that you call from the Activity, which starts up a thread to run doInBackground(). Obviously, you could do a whole load of stuff in here and even use the progress functionality if you want to (I use it on one activity that does multiple steps and it returns an int between 0 and 100 to the calling activity). In this simple case, the task calls one method on a static web service class, which handles the web service client etc. In my case, the web service returns a string which is returned from doInBackground() and is then passed to onPostExecute(). You could consume this here but in my case, the data is to be used by the calling activity, which I call using the reference I attached earlier.

Since onPostExecute() is supposed to be called on the main thread, so you can call directly into the activity and any UI elements of that activity but some people have complained about problems with the wrong thread being used (user error?) in which case, simply pass the calls off via new Handler().post(new Runnable() { public void run() { // Code in here } });

Progress

If you want to report progress, the second parameter to AsyncTask is the type of progress. In one of my activities I use an int to report progress. You do this by calling publishProgress(), which takes a variable list of the type you specify for param 2 in AsyncTask, in my case I pass a step number and a percentage. The step number I use to drive some checkboxes and the precentage to drive a progress bar but you can provide only one value or more depending on what you are doing (but all of the same type).

The other change required in your AsyncTask implementation is to implement onProgressUpdate(), which takes the same arguments as publishProgress() and which, you have probably guessed, gets called on the main thread when publishProgress() is called. It is here that you can call back into your activity and cause some kind of progress meter to update.

@Override
protected void onProgressUpdate(Integer... progress) {
        mActivity.updateProgress(progress[0],progress[1]);
}

Conclusion

It is quite easy to get async capability using this AsyncTask and there are other features not mentioned here such as handling the task being cancelled so please look up the docs here: AsyncTask reference
Post a Comment