BuildMobile: Waiting for Android: UrlJsonAsyncTask

Waiting for Android: UrlJsonAsyncTask

UrlJsonAsyncTaskTest QR Code

Overview

I can’t even count how many times I’ve had to load a ProgressDialog in Android, query JSON from a remote URL, and then return control back to the app once the query has completed. It’s an incredibly common control flow, and one I’m sick of doing over and over again. I’m sure I’m not alone.

In effort to keep this a DRY situation, the class UrlJsonAsyncTask in my collection of Android helpers, com.savagelook.android, is dedicated to the aforementioned task. It reduces what can be an 80+ line of code endeavour, per task, down to about 15 or less.

Before digging in, here’s a few things to consider.

  • This isn’t a basic tutorial, so some knowledge of Android is assumed. In particular, you should understand how Android uses Intents, Activities, and basic event handling.
  • While it’s not necessary, you’ll find this all easier to grasp if you are at least familiar with Android’s AsyncTask class.
  • I make use of a few other classes in com.savagelook.android in UrlJsonAsyncTask that you likely won’t recognize, like JsonHelper and Lazy.Ex. All of these are open source and freely available on GitHub.
  • I don’t get into the blood and guts of how UrlJsonAsyncTask works, just how to use it. Make yourself heard in the comments if you want me to do a follow up.

UrlJsonAsyncTaskTest

The best way to learn how UrlJsonAsyncTask can speed up your development is to see it in action. You can follow along with the following code snippets, or you can pull down the full project source code that I’m basing them on. The project is UrlJsonAsyncTaskTest and it can be freely downloaded or cloned from GitHub.

The app is purposely as bare metal as possible. We are simply going to create a button on our main activity that will launch a UrlJsonAsyncTask and then return the result of its query in a new activity.

Before We Get to the Code…

UrlJsonAsyncTask makes just one “convention over configuration” assumption. In order for all the error handling and timeout features to be handled under the hood, your JSON needs to be in the following format:

{
    "success": true|false,
    "info": "an error message to present to the user when success is false",
    "data": YOUR_DATA
}
  • success is a boolean value indicating whether or not the query was successful on the server side.
  • info gives the user a descriptive, but sanitized description of an error.
  • data is the actual data you wish to return from the query. YOUR_DATA can be any value, or even another JSON object or array.

The (Boring) Code

OK, let’s get the boring stuff out of the way first. Here’s the changes you’ll need to make to a basic Android project to make it look like UrlJsonAsyncTaskTest, if you were too lazy to download/clone it yourself.

AndroidManifest.xml

You need to add 2 things. First, allow Android to talk to the internet by adding a uses-permission clause under the manifest element.

uses-permission android:name="android.permission.INTERNET" /

Second, you need to add the second activity we will open after a successful call to UrlJsonAsyncTask under the application element. It will be appropriately named SecondActivity.

activity android:name=".SecondActivity"/

res/layout/main.xml

This is the layout XML that we will apply to MainActivity. The button here is the one we will use to launch the UrlJsonAsyncTask.

?xml version="1.0" encoding="utf-8"?
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    Button
        android:id="@+id/mainButton"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="Get Remote JSON"/
/LinearLayout

res/layout/second.xml

This is the layout XML for SecondActivity. The TextView here is where we will place the JSON string that is returned from our UrlJsonAsyncTask call.

?xml version="1.0" encoding="utf-8"?
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    TextView
        android:id="@+id/jsonText"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/
/LinearLayout

The (Interesting) Code

Now it’s time to actually extend, create an instance of, and invoke our UrlJsonAsyncTask. We’ll do all this in MainActivity in just a few lines of code. If you aren’t interested in the whole UrlJsonAsyncTaskTest project example and just want to know how to use UrlJsonAsyncTask in your own projects, this is the section to which you need to pay attention.

MainActivity.java

First thing we need to do is make our own class that extends UrlJsonAsyncTask. This will allow us to leverage built-in functionality while giving us the freedom to determine how the resulting JSON is processed. We do this as a private class member of MainActivity. Read the comments for the details on every section.

private class MyTask extends UrlJsonAsyncTask {
    // Create a default constructor
    public MyTask(Context context) {
        super(context);
    }
    // onPostExecute(JSONObject) is what is called when the UrlJsonAsyncTask completes.
    // The JSONObject represents the JSON returned by the remote URL we queried.
    @Override
    protected void onPostExecute(JSONObject json) {
        try {
            // call validateJson() to ensure well formatted JSON
            this.validateJson(json);
            // If the JSON is returned successfully and well formatted, send the
            // JSON "data" as String Extra "json" to SecondActivity
            Intent intent = new Intent(context, SecondActivity.class);
            intent.putExtra("json", json.getString("data").toString());
            startActivity(intent);
        } catch (JSONException e) {
            // If there were exceptions handling the JSON...
            Toast.makeText(context, this.getMessageError(), Toast.LENGTH_SHORT).show();
        } catch (Exception e) {
            // All other uncaught exceptions, like HTTP or IO problems...
            Toast.makeText(context, e.getMessage(), Toast.LENGTH_SHORT).show();
        } finally {
            // You MUST call this or your UrlJsonAsyncTask's ProgressDialog will
            // not close.
            super.onPostExecute(json);
        }
    }
}

Now that we’ve created MyTask, all we have to do is instantiate it, give it a (remote) URL, and let the underlying UrlJsonAsyncTask do the rest of the work. To make it interactive, we will do all this in a click handler for the the mainButton button in the onCreate() function of MainActivity.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    Button button = (Button)findViewById(R.id.mainButton);
    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            // The URL that'll we'll test against
            String remoteJsonUrl = "http://savagelook.com/misc/UrlJsonAsyncTaskTest.php";
            // assign a context and loading message
            MyTask task = new MyTask(MainActivity.this);
            task.setMessageLoading("Getting remote JSON...");
            // Launch the UrlJsonAsyncTask with the test URL
            task.execute(remoteJsonUrl);
        }
    });
}

If everything goes well, MyTask.onPostExecute() will get called as soon as this task returns the requested JSON from http://savagelook.com/misc/UrlJsonAsyncTaskTest.php.

SecondActivity.java

Just to make sure everything went according to plan, we will display the JSON “data” string in SecondActivity. To do this, we add the following few lines of code to SecondActivity.onCreate().

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.second);
    // Get the JSON string we assigned to the intent in MyTask.onPostExecute()
    String jsonString = this.getIntent().getStringExtra("json");
    // assign the JSON string to the TextView
    TextView textView = (TextView)findViewById(R.id.jsonText);
    textView.setText(jsonString);
}

And that’s it. You should now have a UrlJsonAsyncTask successfully connecting to a remote URL and requesting JSON, all the while having a ProgressDialog let the user know what’s going on. As I stated earlier, it’s a very common task, and now it’s a very easy one.

Summary

In the end what we are achieving here is not rocket science. All we are doing is creating a typical control flow for an app that relies on remote data. My aim was to make this flow as trivial as possible, without sacrificing too much flexibility.

I highly encourage you to check out the GitHub repository for all of my com.savagelook.android code. Get under the hood and see exactly how UrlJsonAsyncTask is doing what it’s doing. Try out some of the other properties of the class like timeouts and retries. Take a look at the other helper classes as well. Also, feel free to clone and contribute back.

Hopefully this will help you spend less time on control flow and more time on the unique functionality that your Android apps provide.