Blog List Example with Volley

In my previous iterations of the of the Blog List example, the BlogListService just returned a hardcoded list of BlogPost objects. In this post, we will actually retrieve the list of BlogPost objects from a restful JSON service.

Ideally, you can eliminate much of the HTTP traffic by using some sort of a client side caching layer. This is where Volley comes in.

So, in this post I’ll show two things:

  1. One of the many ways to consume JSON and convert it into java objects
  2. The use of Volley as an asynchronous network thread management and caching solution (replacing the AsyncTaskLoader in the prior examples).

First, the “data service” layer I’m using for this simple example is just an nginx server serving a static JSON file. The static JSON file looks like this:

[
  {"date":"10/20/2013", "title":"Android with Maven", "content":"Android with Maven\n      Ever since making the switch from Ant to Maven 2 back in 2005, I've never looked back. So one of the first things I wanted to know was if I could use maven to build my Android apps. And of course, you can. Here's an example pom.xml file:"},
  {"date":"10/21/2013", "title":"AsyncTaskLoader", "content":"One of the first things I tried to do while developing an Android app is asynchronously populate a statically defined list view. This turned out to be a much more challenging task than I anticipated, and apparently nobody else on the entire world wide web is attempting to do this (or I just didn't google right 0_o).\n\n     Specifically, I wanted to define a ListView (or Spinner or whatever) in a layout XML file, and populate it via data from a restful web service. Initially I tried to do this in the onCreate(), but got the android.os.NetworkOnMainThreadException. So, obviously I needed to pull the data from a restful web service asynchronously. This post is going to explain how I did that.\n\n          I'm going to accomplish this with an AsyncTaskLoader, and I'll use a back to front approach, starting at the service layer and work towards the UI. The example app is an extremely simple app to list some blog posts."}
]

I’m going to use Jackson to map the json to java objects. I could have used gson or probably 10 other json libraries to do this, but I have had good experiences with Jackson and already had a working example in another project. I’m using maven to build my project, so here are the jackson (and joda) dependencies:

    <properties>
        <jackson.version>1.9.13</jackson.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupid>org.codehaus.jackson</groupid>
            <artifactid>jackson-core-asl</artifactid>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupid>org.codehaus.jackson</groupid>
            <artifactid>jackson-jaxrs</artifactid>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupid>org.codehaus.jackson</groupid>
            <artifactid>jackson-mapper-asl</artifactid>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupid>org.codehaus.jackson</groupid>
            <artifactid>jackson-mrbean</artifactid>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupid>org.codehaus.jackson</groupid>
            <artifactid>jackson-xc</artifactid>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupid>com.fasterxml.jackson.datatype</groupid>
            <artifactid>jackson-datatype-joda</artifactid>
            <version>${jackson.version}</version>
        </dependency>
        <dependency>
            <groupid>joda-time</groupid>
            <artifactid>joda-time</artifactid>
            <version>2.3</version>
        </dependency>
        <dependency>
            <groupid>org.joda</groupid>
            <artifactid>joda-convert</artifactid>
            <version>1.5</version>
        </dependency>
    </dependencies>

As for the Volley dependency…well I cheated for this example. To my knowledge, there is no public maven repository housing the Volley artifact at this point (which could be interpreted as Volley is not ready for primetime, or it could be interpreted as the android/volley team has little interest in maven). Anyway, I just pulled the source down with git:

git clone https://android.googlesource.com/platform/frameworks/volley

and copied it into my project. If I was going to use maven in a production application, I would have an artifact repository, and I would have built and deployed a volley jar artifact to it. But for the purposes of blogging an example, this is fine. 

For this example, I’m going to start with the Activity and work my way back to the rest request.

public class ListBlogsActivity extends Activity {
  private RequestQueue mRequestQueue;
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
 
    final BlogListAdapter blogListAdapter = new BlogListAdapter(this, prependHeader(new ArrayList<blogpost>()));
    ListView blogPostListView = (ListView) findViewById(R.id.blogposts);
    blogPostListView.setAdapter(blogListAdapter);
 
    mRequestQueue = Volley.newRequestQueue(this);
    mRequestQueue.add(new VolleyBlogPostRequest(this, blogListAdapter));
  }
 
 
  @Override protected void onStop() {
    super.onStop();
    mRequestQueue.cancelAll(this);
  }
}

Volley requests are placed on a Volley RequestQueue, which under the covers manages a thread pool (which defaults to having 4 threads). There are a lot of things you can configure (number of threads in the thread pool, the underlying HTTP requester, request priority, etc.) but for this example I’m just going to use the defaults.

Note that in onStop() I’m cancelling all requests in the RequestQueue, preventing wasted time and cycles dealing with responses that are going to be ignored.

My JSON describes an array of objects, so my VolleyBlogPostRequest extends JsonArrayRequest:

public class VolleyBlogPostRequest extends JsonArrayRequest {
  public VolleyBlogPostRequest(Context context, BlogListAdapter blogListAdapter) {
    super(
        context.getString(R.string.rest_base_url) + "/BlogPosts.json",
        new BlogPostListener(blogListAdapter),
        new Response.ErrorListener() {
          @Override public void onErrorResponse(VolleyError error) {
            Log.e("ListBlogsActivity.onCreate()", "Volley failed to get BlogPosts! " + error.toString());
          }
        }
    );
  }
}

Nothing too exciting here. Basically I just created this class to keep a lot of the fluff out of the Activity. The interesting things are happening in the BlogPostListener:

public class BlogPostListener implements Response.Listener<JSONArray> {
  private BlogListAdapter blogListAdapter;
  private ObjectMapper mapper = new ObjectMapper();
 
  public BlogPostListener(BlogListAdapter blogListAdapter) {
    this.blogListAdapter = blogListAdapter;
    SimpleModule module = new SimpleModule("JSONModule", new Version(2, 0, 0, null));
    module.addSerializer(DateTime.class, new CustomDateSerializer());
    module.addDeserializer(DateTime.class, new CustomDateDeserializer());
    mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.registerModule(module);
  }
 
  @Override public void onResponse(JSONArray response) {
    try {
      List<BlogPost> blogPosts = mapper.readValue(response.toString(), new TypeReference<List<BlogPost>>() {});
      blogListAdapter.setData(BlogListUtil.prependHeader(blogPosts));
    } catch (Exception e) {
      throw new RuntimeException("Failed!", e);
    }
  }
}

The BlogPostListener’s onResponse is called when the response comes back from the endpoint we provided to the JsonArrayRequest constructor. As you can see, using Jackson to map a JSON string to java objects is pretty simple. Also, I’ve moved the prependHeader method out of the activity an into a static utility class (where it really belongs anyway).