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.

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.

// /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;
  }
}
// /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);
  }
}
<!-- /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.

// /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);
    }
  }
<!-- /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"
    },
    // ...
  ]
}
// /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;
    }
}
<!-- /app/main/AndroidManifest.xml -->
<!-- ... -->
  <uses-permission android:name="android.permission.INTERNET" />

  <application
    android:name=".VolleyApplication"
    ...>
  </application>
// /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);
    }
  }

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

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

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

// /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!

Get notified when I publish new tutorials and articles Topics include Android, Rails, iOS and Docker. Delivered straight to your inbox. Unsubscribe anytime.

Clone the code on github

Learn how to keep your Rails apps fast, secure and running in top condition.

The Ruby and Rails worlds move fast, so it can feel like no time at all before your app starts to feel left behind.

Slow pages, memory, new gems, a constant stream of security patches, changing third-party APIs, and growing demands on your server's resources all work tirelessly against your app.

While your app launched with a strong foundation— the latest gems, employing best practices, and developing a thorough test suite—it needs constant attention and maintenance to keep running in top condition.

In Keep Your Ruby on Rails App Healthy, you'll learn about the tools and techniques for keeping your Ruby on Rails app running smoothly, and how to apply them to your apps.

The course delivers a range of practical and actionable lessons to enhance and protect your app right away, from simple, quick–win optimisations and setting up thorough monitoring to reviewing your site's performance, and checking your code against security vulnerabilities current best practices.

“I really enjoyed the book. A lot of quick wins for me to implement and makes me realise I can do more with guard.” –– Ben (@r0_0tKat)

Keep Your Ruby on Rails App Healthy is available as both an email course, and as a premium download edition.

The email course is split up into 7 lessons, delivered to your inbox over the course of a month.

The premium edition is available for instant download in PDF, ePub and Mobi (Kindle) format.

Start Email Course Buy Premium $9 (plus EU VAT)

howto programming android api tutorial volley networking restful json
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.