13 August 2014 · About 21 minutes read

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 removes lots of boiler-plate code, and lets you concentrate on the details of your app rather than dealing with low-level networking technologies.

Volley App Screenshot

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.

Clone the code on github

Spin up a new Android Studio Project

As before, I’ll use Android Studio for this tutorial. Development of Android Studio continues to be rapid, and it is now pretty stable in general use. Since the last tutorial, it has been upgraded to a beta release. If you haven’t already made the jump, I highly recommend giving Android Studio over Eclipse.

  • Open Android Studio and choose New Project
  • Name your new app VolleyApp2 under the example.com company name (this will automatically set your package name to com.example.VolleyApp2).
  • Choose Phone and Tablet and API 10: Android 2.3.3 (Gingerbread) for the Minimum SDK. This will automatically configure your app to use the Android Support Libraries to support older devices.
  • Choose Blank Activity with Fragment. We’ll use a ListView embedded in a Fragment to browse our remote data.
  • Name your activity ImagesActivity, layout activity_images and fragment layout fragment_images.
  • Finally, click Finish to create your new project.

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

Add Volley Dependency

As before, we’ll use this helpful gradle mirror of Volley to easily import the library into our project:

// /app/build.gradle

// ...

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  compile 'com.android.support:appcompat-v7:20.0.0'

  compile 'com.mcxiaoke.volley:library:[email protected]'
}

Android Studio will now warn that it needs to Sync the Gradle config. This ensures that our project’s configuration matches that of Gradle within Android Studio. Synchronising will also download the new depencency on Volley, making its classes available within our app.

Add a ListView and Adapter to our Fragment

We’ll start by setting 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.

Add a Data Model

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;
  }
}
  • Open the ImagesActivity.java file in Android Studio. At the end of the file, you’ll see that Android Studio has created a PlaceholderFragment inner class. This is the fragment that in displayed in our Activity.
  • Use Android Studio’s Rename refactor to change the name of this class to ImagesFragment. To do this, position the cursor over the PlaceholderFragment and choose Refactor->Rename [Shift+F6]. In the highlight box that appears, change PlaceholderFragment to be ImagesFragment.
  • Create a new class, ImageRecordsAdapter that will hold our list of ImageRecords and adapt them to the fragment’s 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:
<!-- /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="@drawable/ic_launcher"
    android:id="@+id/image1"/>

  <TextView
    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.

  • Inside ImagesFragment, add a new ImageRecordsAdapter field (mAdapter) and initialise it in the onActivityCreated method:
// /app/src/main/java/com/example/volleyapp2/ImagesActivity.java

// ...
  public static class ImagesFragment 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_images.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):
// /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;
    }
}
  • Specify the new VolleyApplication class in your app’s AndroidManifest.xml file. Now is also a good time to add the INTERNET permission:
<!-- /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 ImagesFragment:
// /app/src/main/java/com/example/volleyapp2/ImagesActivity.java

// ...

  public static class ImagesFragment 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 ImagesFragment 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 a new method, parse() within ImagesFragment to loop through the array of images in the JSON feed, and create a new ImageRecord for each:
// /app/src/main/java/com/example/volleyapp2/ImagesActivity.java

// ...

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

      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;
    }
  }

  • Finally, 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 ImagesFragment 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();   
            }
          }

          // ...
    }

We’ll need to add the swapImageRecords() method to ImageRecordsAdapter. This simply clears the existing data, adds all the new records, and notifies the adapter that our data has been changed:

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

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

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

    notifyDataSetChanged();
  }

Before we can run the app, we’ll need to override the 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 to us by Volley. Just as with the JSONObjectRequest, we need to let Volley know which RequestQueue should be used to manage the download 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 already downloaded images in memory, only triggering a network download if the cache doesn’t return anything.

Caching

Unfortunately, Volley doesn’t provide a built-in cache, so we have to write one ourselves. Fortunately, this is relatively simple using the LruCache class provided by Android:

// /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, let’s create an ImageLoader for the adapter to use:

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

public class ImageRecordsAdapter extends ArrayAdapter<ImageRecord> {
  private 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 handle the 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 should be presented with the results of the parsed feed:

Consuming Remote JSON APIs

With that, 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 the retrieved data using SQLite, etc.), this tutorial has shown the core of what needs to be done.

Whilst long, 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!


Thanks for reading! If you’ve liked this post, please let me know by leaving your comments and feedback on Twitter.

Get notified when I publish new tutorials and articles. Delivered straight to your inbox. Unsubscribe anytime.

Clone the code on github

Chris Blunt
Chris Blunt @cblunt
Chris is the founder of Plymouth Software. As well as code and business, he enjoys being a Dad, swimming, and the fine art of drinking tea.