Android ListViews with Dynamic Data

Ed’s Note: This is an article based on a tecnique presented in the book Android UI In Action by Neil Davies. For Source Code, Sample Chapters, the Author Forum and other resources, go to http://www.manning.com/davies/

Android UI In Action Book Cover

In a lot of cases, ListViews need to display sets of dynamic data that are loaded at runtime. Have a think about some of the apps you might use, news reading apps, social media, TV guides, and so on. All of these applications will load data at runtime from a server. Often, applications will display this data in a List. To do this, they’ll commonly use a REST Web Service, where requests are made using HTTP messages and responses are returned as serialized objects, commonly in an XML or JSON format. Let’s take a look at an example so that we can explore some of these concepts.

A sample dynamic ListView using Picasa

In this example, we are going to build a ListView that loads data from the Picasa web API and displays it in list. We’ll do this in a number of steps.

  • First, we’ll create a ListView with an empty data set.
  • Secondly, the data for the feed will be retrieved.
  • Finally, we’ll lazily load the images for each item in the List as they are needed

To do this, we’ll make use of Async tasks to make the request to the Web API, so that we don’t block the main UI thread. We’ll request that the data is returned as a JSON string and we’ll decode the JSON using the very useful and easy to use GSON library. Before we look at how to code this, let’s take a look at the steps involved in creating this type of ListView.

Initializing the ListView

Before we can begin loading data for the ListView, we first need to set it up in an initial empty state. To do this, we’ll use a ListActivity that will load the layout for the list. The layout will contain a list view and an empty view. The ListActivity will then create an Adapter that is empty and assign it to the ListView. Since the Adapter is empty and does not contain any data, the ListView will display the empty view, in this case, a TextView loaded from the layout. Figure 1 outlines the initialization process.

Initializing an empty ListView

Figure 1: Initializing an empty Listview

Load data with an Async Task

Once we have initialized the ListView, we then need to retrieve the data to populate it. To do this, the ListActivty starts an Asynchronous task to load data from the Picasa service. Here, we make a request to the Picasa API via a HTTP get. Once we get the data back from Picasa, we update the ListAdapter so that it now has a populated ArrayList of this data, we then call notifyDataSetChanged so that the ListView can be updated and drawn with the populated data. Figure 2 gives an overview of the process for retrieving the data from the Picasa service.

Loading Picasa data with Async Task

Figure 2 Loading Picasa data with Async Task

Loading the image bitmaps

The data that we’ve just loaded and populated into the Arraylist contains information such as title, description, and URL paths to images. It does not contain the actual bitmap data for the images themselves. Therefore, the last thing that needs to be done is to load the image data for the images in each list item. As already mentioned, the image data is loaded lazily, and, until the list item with an image is displayed, the data for the image will not be retrieved. So how is the image data loaded only when we need it?

Each Adapter has a getView method that is called when a List needs to display a List item. So, as you scroll through a List, getView will be called each time a new view is displayed. It is here in getView that we implement the lazy loading of the image data. Remember that we have already populated an ArrayList of the feed data. So, each time getView is called, we simply assign the text strings for the title and description to the appropriate views in the list item.

For each image, we need to do a little extra work. The feed data only gives us a URL for the image. We use this URL to make another Asynchronous call to the Picasa server from within the Adapters getView method. The class that we will use to make this Asynchronous call is ImageDownloader. Once the data is loaded, we update the ImageView for the respective list item. To add a little UI magic, we also use a crossfade effect between the default image and the newly loaded image.

Figure 3 describes this process.

Loading the Image for each list item on the ListViews call to getView

Figure 3 Loading the Image for each list item on the ListViews call to getView

Essentially, there are three steps to our example: first, we initialize an empty ListView; second, we load the feed data; and, to finish, we then lazily load the image data as each item in the list is displayed.

Implementing a dynamic ListView

With the overview of how we are going to implement our ListView fresh in our minds, let’s have a look at the implementation details of how we really go about dynamically loading data into a ListView. The first thing we will have to do is create the XML for our Listview layout, as shown in listing 1.

Listing 1 XML for layout containing the Listview

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:gravity="center"
       android:orientation="vertical" >

       <ListView
          android:id="@android:id/list"
          android:fadingEdge="vertical"
          android:fadingEdgeLength="10dp"
          android:longClickable="true"
          android:listSelector="@drawable/list_selector_background"
          android:layout_width="match_parent"
          android:layout_height="match_parent" >
       </ListView>
<!-- -->                                                        #1
       <TextView
          android:id="@android:id/empty"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_gravity="center"
          android:text="Loading feed data..." />
<!-- -->                                                        #2
</LinearLayout>
#1 Listview with id of "list"
#2 TextView with id of "empty"

Here, we have a LinearLayout that contains two items, a Listview (#1) and a TextView (#2). The ListView is there to display the list of data that we will load, but what about the TextView? This view is displayed only when we don’t have data, it could be any type of view we like but for now we are just going to use a TextView that displays the String “Loading feed data.” The important point to notice here is that both of ListView and the TextView have specific ids that must be correct. The ListView must have an id of @android:id/list and the TextView must have and id of @android:id/empty. Otherwise, the ListActivty will not know which views to use for the List and its empty alternative view when there is no data to display.

So that’s the main layout done, let’s not forget that we also need a layout for each list item. For this, we’ll just reuse the list item layout that we defined in the previous example here it is again in listing 2.

Listing 2 XML for the layout of each list item

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
       xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="wrap_content" >

       <ImageView
          android:id="@+id/listImage"
          android:layout_width="100dp"
          android:layout_height="100dp"
          android:layout_alignParentLeft="true"
          android:layout_centerVertical="true"
          android:layout_margin="10dp"
          android:background="#ffcccccc" />

       <TextView
          android:id="@+id/listTitle"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_alignTop="@+id/listImage"
          android:layout_toRightOf="@+id/listImage"
          android:text="A List item title"
          android:textSize="16sp"
          android:textStyle="bold" />

       <TextView
          android:id="@+id/listDescription"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:layout_below="@+id/listTitle"
          android:layout_marginTop="5dp"
          android:maxLines="4"
          android:layout_toRightOf="@+id/listImage"
          android:text="The List item description"
          android:textSize="14sp" />

</RelativeLayout>

Having defined the Layouts, we now need a ListActivity. Listing 3 shows our ListActivity called DynamicListviewActivity. This is still quite a simple Activity.

Listing 3 ListActivity for the Dynamic ListView

public class DynamicListViewActivity extends ListActivity {

       public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.list);                              #1

          ImageListAdapter adapter = new ImageListAdapter(this);
          setListAdapter(adapter);                                    #2

          LoadFeedData loadFeedData = new LoadFeedData(adapter);
          loadFeedData.execute();                                     #3
       }
}
#1 Load list layout defined in XML
#2 Create and set Adapter for the ListView. Initially this adapter will be empty and not contain data.
#3 Instantiate and execute LoadFeedData, to load the Picasa data Asynchronously in the background.

The Adapter in this Activity does a little more than the Adapter in our previous example, but, for now, let’s look at the LoadFeedData class that we instantiate and call execute on #3 in the onCreate method of our ListActivity. The LoadFeedData class hopefully has a fairly self-explanatory name. Its job is to load the feed data; in this example, it will retrieve that data from the Picasa web API. Let’s take a look at this class.

Listing 4 LoadFeedData class used for Loading the feed data

public class LoadFeedData extends
          AsyncTask> {                           #1

       private final String mUrl =                               #2
          "http://picasaweb.google.com/data/feed/api/all?kind=photo&q=" +
          "sunset%20landscape&alt=json&max-results=20&thumbsize=144c";

       private final ImageListAdapter mAdapter;

       public LoadFeedData(ImageListAdapter adapter) {           #3
          mAdapter = adapter;
       }

       private InputStream retrieveStream(String url) {
          DefaultHttpClient client = new DefaultHttpClient();
          HttpGet httpGet = null;
          httpGet = new HttpGet(url);

          HttpResponse httpResponse = null;
          try {
             httpResponse = client.execute(httpGet);
             HttpEntity getResponseEntity = httpResponse.getEntity();
             return getResponseEntity.getContent();
          } catch (IOException e) {
             httpGet.abort();
          }
          return null;
       }

       @Override
       protected ArrayList<Entry> doInBackground(Void... params) {
          InputStream source = retrieveStream(mUrl);                   #4
          Reader reader = null;
          try {
             reader = new InputStreamReader(source);
          } catch (Exception e) {
             return null;
          }
          Gson gson = new Gson();
          SearchResult result = gson.fromJson(reader,SearchResult.class);      #5
          return result.getFeed().getEntry();
       }

       protected void onPostExecute(ArrayList<Entry> entries) {
          mAdapter.upDateEntries(entries);                        #6
       }
}
#1 LoadFeedData extends AsyncTask
#2 Set up URL, that makes up a query to the Picasa API
#3 LoadFeedData constructor
#4 retrieveStream method call returns an Inputstream to Picasa data
#5 Call fromJson method to transform JSON strings into objects
#6 Call UpdateEntries on the Adapter to populate it with retrieved array of data

Taking a closer look at LoadFeedData, we can see that it is an Async class. The Android Async class allows us to execute code on a different thread to the main Android UI thread. By calling the execute method on an Async task, we instruct it to execute any code that is in its doInBackground method on a separate background thread. A full explanation of the Async task is beyond the scope of this example, but more information on these very useful classes can be found on the Android developers’ web site at http://developer.android.com/reference/android/os/AsyncTask.html.

Stepping through the code in this class, we can see that the first thing we do is set up a URL (#2). This URL makes up a query to the Picasa API. The Arguments state that we want to retrieve a maximum of 20 images that are of thumbnail size and relate to landscapes and sunsets. Also we state that we want the data to be returned in a JSON format. For the purpose of keeping things simple this URL is hard coded, but it is of course possible to build URLs that can make queries of many varying types at runtime. The constructor of LoadFeedData class take a reference to the Adapter for the ListView (#3), so that we can notify it when we have retrieved the feed data from the Picasa API.

All the main work of this class happens in the doInBackground method. Here we use the private method retrieveStream (#4) to return an InputStream. The InputStream contains a serialized data object in a JSON format. To transform the JSON string into objects that we can work with, we use a class from the GSON library and call the method fromJson (#5) while passing the InputStream a class that we want the JSON data mapped into.

To parse the JSON data we use GSON, which is library. The GSON library is a powerful way to deserialize data from existing JSON strings and can also be used to serialize objects to JSON strings. More information on GSON can be found here: https://sites.google.com/site/gson/Home.

All we have to do to get the data from the JSON string is to supply classes that have attributes that map directly to the attributes in the JSON string. The GSON class will take care of the details for us.

Once we have retrieved the data from the Picasa web API we then pass this data as an array of Entry objects to the Adapter via the UpdateEntries method (#6). The Adapter will then have a populated ArrayList of image data and also notify the ListView that this is the case so that the ListView can display this data. Let’s take a closer look at the Adapter that we use in this example.

Listing 5 ImageListAdapter used to populate ListView with data and images

public class ImageListAdapter extends BaseAdapter {

       private Context mContext;

       private LayoutInflater mLayoutInflater;                              #1

       private ArrayList<Entry> mEntries = new ArrayList<Entry>();          #2

       private final ImageDownloader mImageDownloader;                      #3

       public ImageListAdapter(Context context) {                           #4
          mContext = context;
          mLayoutInflater = (LayoutInflater) mContext
                   .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
          mImageDownloader = new ImageDownloader(context);
       }

       @Override
       public int getCount() {
          return mEntries.size();
       }

       @Override
       public Object getItem(int position) {
          return mEntries.get(position);
       }

       @Override
       public long getItemId(int position) {
          return position;
       }

       @Override
       public View getView(int position, View convertView,
             ViewGroup parent) {                                           #5
          RelativeLayout itemView;
          if (convertView == null) {                                        #6
             itemView = (RelativeLayout) mLayoutInflater.inflate(
                      R.layout.list_item, parent, false);

          } else {
             itemView = (RelativeLayout) convertView;
          }

          ImageView imageView = (ImageView)
             itemView.findViewById(R.id.listImage);                        #7
          TextView titleText = (TextView)
             itemView.findViewById(R.id.listTitle);                        #7
          TextView descriptionText = (TextView)
             itemView.findViewById(R.id.listDescription);                  #7

          String imageUrl = mEntries.get(position).getContent().getSrc();   #8
          mImageDownloader.download(imageUrl, imageView);                   #9

          String title = mEntries.get(position).getTitle().get$t();
          titleText.setText(title);
          String description =
             mEntries.get(position).getSummary().get$t();
          if (description.trim().length() == 0) {
             description = "Sorry, no description for this image.";
          }
          descriptionText.setText(description);

          return itemView;
       }

       public void upDateEntries(ArrayList<Entry> entries) {
          mEntries = entries;
          notifyDataSetChanged();
       }
}
#1 The layout inflater used to inflate the list item layouts
#2 Entry classes to store data from Picasa service, This array will initially be empty, but will be populate by the LoadDataFeed Async task
#3 The ImageDownloader class. Another Async task used for lazily loading the Image for each list item.
#4 Constructor for the ImageListAdapter. Set up references to context, create layout inflater and ImageDownloader.
#5 getView method of the Adapter, this method is called every time the ListView needs to display a list item.
#6 Check if covertView is null to see if we can reuse an unused view passed from the ListView.
#7 Get references to Image, title and description for the list item, so that we can populate with data from Picasa service.
#8 Get the URL for the list item image from the Picasa data.
#9 Call download method on ImageDownloader to start an Async task that will download the Image data and update the image in the list item.

Here we can see that our Adapter is called ImageListAdapter. This adapter is a fairly typical Adapter with all the methods that we would expect. Of course, getView (#5) is where most of the work is done.

When is getView called?
Remember that the getView method of an Adapter is called every time a List needs to display a List Item. So, when a List item is initially displayed, this method will be called a number of times so that each list item currently displayed can be populated with the data contained in the Adapter. As we scroll the list and new list items come into view, getView will again be called as each list item is displayed.

Our getView method is a fairly standard example. The whole aim of this method is to populate each list item with data, in this case, the ArrayList of Entry classes that we retrieved from the Picasa web API.

The one thing that is done differently here is how we populate the Image for the list item. Here we get the URL (#8) for the image for this list item, and pass both the URL and a reference to the ImageView for the list item to the ImageDownloader class by calling its download method (#9). This method will asynchronously load the data for the image and update the Image once it’s done. This is done in a lazy fashion because getView will only be called when a list items needs to be displayed, hence the image will only be retrieved only when a list item is shown and the image is needed.

The main aim of the ImageDownloader is to asynchronously load images, but it also caches images in memory. Our ImageDownloader class has a few tweaks made to it so we can add the possibility of caching images to disk and also so we can add a crossfade transition between the initial image we show in the list and the final downloaded image.

To add the crossfade functionality we use another class called CrossFadeDrawable. This class is again based on previously written code. Romain Guy, a Google engineer, originally created this class in an open source project called shelves, which has some nice code techniques in it and is worth taking time to look at. We have made a tweak to this class so it does exactly what we need. Here our tweak is to add a matrix transformation that scales our downloaded image to the size of our start image, since the original class assumed that the default start image was the same size as the downloaded end image.

If you'd like to know more about the ImageDownloader and CrossFadeDrawable classes the full source code for these classes and the other classes in this example are available on this book’s companion website.

Summary

Great, that’s it! We covered all the different techniques we need to load dynamic data and images and display them in a list, and to put some icing on the cake we’ve also added a few nice additions such as an empty loading screen and a transitional crossfade between each list items default image and its final downloaded image.

You might also like...

Comments

About the author

Dan Maharry

Dan Maharry United Kingdom

Dan Maharry is the editor of DeveloperFusion. He has covered the latest in software development since the mid 90s. Not wishing to preach what he doesn't practice, Dan has also worked as a profes...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.” - Antoine de Saint Exupéry