Android: Consuming a Remote JSON API with Volley

In the previous tutorial, you learned how to use Volley's built-in networking tools to quickly download a network resource and display the results.

Volley is a networking library that removes lots of boiler-plate code when dealing with handling data across a network. It provides a number of helpful classes to simplify downloading and parsing data and images, and lets you concentrate on the details of your app rather than dealing with low-level networking technologies.

In this post, we'll start a new project and go further into Volley, using the built-in JSONObjectRequest and ImageRequest classes to make use of the remote data. We'll also use Volley's custom NetworkImageView widget to easily (and safely) load a remote image into our UI.

Volley App Screenshot

Create a new Android Studio Project

As in the previous tutorial, we'll use Android Studio to build the project. Once installed, create a new project for our app using the Android New Project setup:

→ Create a new project in Android Studio with the following settings:

Once your project has been created and Gradle has updated, hit Build->Run (Ctrl+R) to test the new app. You should see the standard Hello World! message on your device or emulator.

Add Volley Dependency to your Gradle File

Volley is now officially available via Gradle from the Android Open Source Project (this tutorial previously used this mirror, which is now deprecated).

→ Add the Volley dependency to your module's build.gradle file:

// /app/build.gradle

// ...

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  compile 'com.android.support:appcompat-v7:25.3.1'
  // ...
  compile 'com.android.volley:volley:1.0.0'
}

Android Studio will notify you that it needs to Sync the Gradle config. This ensures that your project's configuration matches that of Gradle within Android Studio. Synchronising will also download the Volley as a dependency, making its classes available within our app.

If you don't see this notification, choose Build->Rebuild Project... to refresh and sync the project.

Create a Data Model

We'll set up a simple UI to hold our list of images and their titles. This will use the standard Adapter pattern that is common throughout Android.

Let's start by creating a data model to represent the downloaded data. When consuming a web service, you'll likely want to store a local cache of the downloaded data for using offline. For Android apps, this is likely to be a SQLite database to persist the database.

For this tutorial, though, we'll just cache the data - a list of image URLs and their titles - into an ArrayList of simple ImageRecord objects.

→ Create a new class, ImageRecord with fields for the image URL and title:

// /app/src/main/java/com/example/volleyapp2/ImageRecord.java

public class ImageRecord {
  private String url;
  private String title;

  public ImageRecord(String url, String title) {
    this.url = url;
    this.title = title;
  }

  public String getTitle() {
    return title;
  }

  public String getUrl() {
    return url;
  }
}

→ Create a new class, ImageRecordsAdapter:

This will hold our list of ImageRecords and adapt them to ImagesActivityFragment UI.

// /app/src/main/java/com/example/volleyapp2/ImageRecordsAdapter.java
public class ImageRecordsAdapter extends ArrayAdapter<ImageRecord> {
  public ImageRecordsAdapter(Context context) {
      super(context, R.layout.image_list_item);
  }
}

We haven't yet created the image_list_item layout. Android Studio will notice this and warn us to create the resource. Do that now using the following simple layout.

→ Create a new layout file for image_list_item:

<!-- /app/src/main/res/layout/image_list_item.xml -->
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:orientation="horizontal"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">

  <com.android.volley.toolbox.NetworkImageView
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:scaleType="centerCrop"
    tools:src="@mipmap/ic_launcher"
    android:id="@+id/image1"/>

  <TextView
    android:padding="8dp"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:textAppearance="?android:textAppearanceLarge"
    tools:text="Image Name"
    android:id="@+id/text1"/>
</LinearLayout>

Notice the use of com.android.volley.toolbox.NetworkImageView instead of the normal ImageView widget. NetworkImageView is a special widget provided by Volley that adds a simple setImageUrl method.

Volley will automatically handle downloading remote images into the NetworkImageView for us, cancelling any requests in progress if the NetworkImageView is scrolled off the screen.

→ Add a new ImageRecordsAdapter field (mAdapter) inside ImagesActivityFragment:

You'll also initialise it in the fragment's onActivityCreated method.

// /app/src/main/java/com/example/volleyapp2/ImagesActivity.java

// ...
  public static class ImagesActivityFragment extends Fragment {
    private ImageRecordsAdapter mAdapter;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
      super.onActivityCreated(savedInstanceState);

      mAdapter = new ImageRecordsAdapter(getActivity());

      ListView listView = (ListView) getView().findViewById(R.id.list1);
      listView.setAdapter(mAdapter);
    }
  }

→ Open the fragment's layout and remove the placeholder TextView, replacing it with a ListView to display the list of images as they are loaded:

<!-- /app/src/main/res/layout/fragment_image.xml -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  tools:context=".ImagesActivity$PlaceholderFragment">

  <ListView
    android:id="@+id/list1"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
</RelativeLayout>

Running the app, you should now see a blank screen. It's time to fetch some remote data using Volley and populate our list!

Fetch a remote JSON feed using Volley

We'll use Volley in the same way as before to grab a JSON feed. This feed contains a list of images and their titles in the following format:

{
  'images': [
    {
      'url': "http://assets.example.com/image1.jpg",
      'title': "Sunset"
    },
    {
      'url': "http://assets.example.com/image2.jpg",
      'title': "Beach"
    },
    // ...
  ]
}

Start by extending the Android Application class and adding a global RequestQueue field (see the previous tutorial for more details on this).

→ Create a new VolleyApplication class:

// /app/src/main/java/com/example/volleyapp2/VolleyApplication.java

public class VolleyApplication extends Application {
    private static VolleyApplication sInstance;

    private RequestQueue mRequestQueue;

    @Override
    public void onCreate() {
        super.onCreate();

        mRequestQueue = Volley.newRequestQueue(this);

        sInstance = this;
    }

    public synchronized static VolleyApplication getInstance() {
        return sInstance;
    }

    public RequestQueue getRequestQueue() {
        return mRequestQueue;
    }
}

→ Set the new VolleyApplication class in your app's AndroidManifest.xml file:

Now is also a good time to add the android.permission.INTERNET permission, so our app can download the remote JSON file.

<!-- /app/main/AndroidManifest.xml -->
<!-- ... -->
  <uses-permission android:name="android.permission.INTERNET" />

  <application
    android:name=".VolleyApplication"
    ...>
  </application>

With an accessible, global RequestQueue ready to receive Volley requests, we can now add a new private method fetch to ImagesActivityFragment.

→ Add the following fetch() method:

// /app/src/main/java/com/example/volleyapp2/ImagesActivity.java

// ...

  public static class ImagesActivityFragment extends Fragment {
    // ...
    private void fetch() {
      JsonObjectRequest request = new JsonObjectRequest(
        "http://cblunt.github.io/blog-android-volley/response2.json",
        null,
        new Response.Listener<JSONObject>() {
          @Override
          public void onResponse(JSONObject jsonObject) {
            // TODO: Parse the JSON
          }
        },
        new Response.ErrorListener() {
          @Override
          public void onErrorResponse(VolleyError volleyError) {
            Toast.makeText(getActivity(), "Unable to fetch data: " + volleyError.getMessage(), Toast.LENGTH_SHORT).show();
          }
      });

      VolleyApplication.getInstance().getRequestQueue().add(request);
    }
  }

→ Add a call to fetch() at the end of onActivityCreated:

// /app/src/main/java/com/example/volleyapp2/ImagesActivity.java

// ...

  public static class ImagesActivityFragment extends Fragment {
    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
      mAdapter = new ImageRecordsAdapter(getActivity());

      ListView listView = (ListView) getView().findViewById(R.id.list1);
      listView.setAdapter(mAdapter);

      fetch();
    }

Parsing the Data

The above code sends a remote request (asychronously) via Volley and calls back with a response, either a successfully parsed JSONObject from the response, or an error (such as a 404).

In our onResponse handler, we'll forward the parsed JSONObject onto a new method, parse which will convert the JSON feed into a list of ImageRecords.

→ Add the parse() method to ImagesActivityFragment:

This method to loop through the array of images in the JSON feed, and create a new ImageRecord for each one.

// /app/src/main/java/com/example/volleyapp2/ImagesActivity.java

// ...

  public static class ImagesActivityFragment extends Fragment {
    // ...
    private List<ImageRecord> parse(JSONObject json) throws JSONException {
      ArrayList<ImageRecord> records = new ArrayList<>();

      JSONArray jsonImages = json.getJSONArray("images");

      for(int i =0; i < jsonImages.length(); i++) {
        JSONObject jsonImage = jsonImages.getJSONObject(i);
        String title = jsonImage.getString("title");
        String url = jsonImage.getString("url");

        ImageRecord record = new ImageRecord(url, title);
        records.add(record);
      }

      return records;
    }
  }

→ Update the onResponse() handler to call our new parse() method and update the ArrayAdapter:

// /app/src/main/java/com/example/volleyapp2/ImagesActivity.java

// ...

  public static class ImagesActivityFragment extends Fragment {
    // ...
    private void fetch() {
      JsonObjectRequest request = new JsonObjectRequest(
        "http://cblunt.github.io/blog-android-volley-2/response.json",
        null,
        new Response.Listener<JSONObject>() {
          @Override
          public void onResponse(JSONObject jsonObject) {
            try {
              List<ImageRecord> imageRecords = parse(jsonObject);

              mAdapter.swapImageRecords(imageRecords);
            }
            catch(JSONException e) {
              Toast.makeText(getActivity(), "Unable to parse data: " + e.getMessage(), Toast.LENGTH_SHORT).show();
            }
          }

          // ...
    }

Next, we need to add the swapImageRecords() method to ImageRecordsAdapter. This simply clears the existing data, adds all the new records, and notifies the adapter that the underlying data has been updated.

→ Add the ImageRecords() method:

// /app/src/main/java/com/example/volleyapp2/ImageRecordsAdapter.java

  public void swapImageRecords(List<ImageRecord> objects) {
    clear();

    for(ImageRecord object : objects) {
        add(object);
    }

    notifyDataSetChanged();
  }

Before the app can be run, we need to override ImageRecordAdapter's getView() method to specify how the data should be adapted to the UI.

→ Override getView and inflate the image_list_item layout:

// /app/src/main/java/com/example/volleyapp2/ImageRecordsAdapter.java

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
      if(convertView == null) {
          convertView = LayoutInflater.from(getContext()).inflate(R.layout.image_list_item, parent, false);
      }

      // NOTE: You would normally use the ViewHolder pattern here
      NetworkImageView imageView = (NetworkImageView) convertView.findViewById(R.id.image1);
      TextView textView = (TextView) convertView.findViewById(R.id.text1);

      ImageRecord imageRecord = getItem(position);

      imageView.setImageUrl(imageRecord.getUrl(), mImageLoader);
      textView.setText(imageRecord.getTitle());

      return convertView;
  }

Notice the use of setImageUrl to load an image directly into the NetworkImageView. This is provided by Volley.

Just as with the JSONObjectRequest, we need to let Volley know which RequestQueue should be used to manage the downloaded images.

For images, though, Volley provides another layer of abstraction - the ImageLoader class. This takes a RequestQueue and an object implementing the ImageLoader.ImageCache interface.

The ImageCache lets us store downloaded images in memory, only triggering a network download if image isn't present in the cache.

Caching

Unfortunately, Volley doesn't provide a built-in cache, so we'll need to write our own. Fortunately, this is relatively simple using the LruCache (least—recently used) class provided by Android.

→ Create a new class, BitmapLruCache, with the following code:

// /app/src/main/java/com/example/volleyapp2/BitmapLruCache.java

public class BitmapLruCache extends LruCache<String, Bitmap>
    implements ImageLoader.ImageCache {

  static int getMaxCacheSize() {
      int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
      return maxMemory / 8;
  }

  public BitmapLruCache() {
    super(getMaxCacheSize());
  }

  @Override
  protected int sizeOf(String key, Bitmap value) {
    return value.getRowBytes() * value.getHeight();
  }

  @Override
  public Bitmap getBitmap(String url) {
    return get(url);
  }

  @Override
  public void putBitmap(String url, Bitmap bitmap) {
    put(url, bitmap);
  }
}

With a simple in-memory cache ready to use, we can now create an new instance of ImageLoader for our adapter to use.

→ Add the following code to create an instance of ImageLoader in the adapter's constructor:

// /app/src/main/java/com/example/volleyapp2/ImageRecordsAdapter.java

public class ImageRecordsAdapter extends ArrayAdapter<ImageRecord> {
  private final ImageLoader mImageLoader;

  public ImageRecordsAdapter(Context context) {
    super(context, R.layout.image_list_item);

    mImageLoader = new ImageLoader(VolleyApplication.getInstance().getRequestQueue(), new BitmapLruCache());
  }

  // ...
}

With that, your app is now ready to download and parse downloaded data, and any external images in the feed. The NetworkImageView (and ImageLoader) will automatically retrieve and display the images.

→ Run your app, and you will be presented with the results of the parsed feed:

Volley App Screenshot

Consuming Remote JSON APIs

You have successfully used Volley to consume a simple JSON API. Although there's lots more that can be done (e.g. disk caching, locally caching and synchronising the retrieved data using SQLite, etc.), this tutorial has shown the core of what needs to be done.

Complementing Volley with Picasso

Although Volley provides some great helpers for downloading, caching and displaying images from the network, it can be a little low-level sometimes. I've recently been using Square's Picasso library to handle the image downloading side of things.

Not only does Picasso provide a very nice API, it handles disk and memory based caching without any extra configuration or effort from you - and provides some nice built-in transition effects.

Picasso is a topic for another post, but you can find out more at http://square.github.io/picasso/.

Next Steps

I hope you've found this tutorial helpful for learning about Android's powerful new networking framework. Using Volley to handle your network requests removes vast swathes of boiler-plate code and edge-case tests which means you can concentrate on developing your app's valuable features!

Clone the code on github