Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Android

Switching between List Empty and Busy Indicators for Android ListView

5.00/5 (2 votes)
24 Feb 2014CPOL2 min read 18.9K  
I wanted to show a no data legend when there's nothing to show for an asynchronously loaded list and a busy indicator whilst items are being sought/loaded.

Introduction

This one really belongs to the Sybil Fawlty, "Dept. Of The Bleedin' Obvious", library, but as it took me an hour or so to work out I hope posting this might save some of you out there that hour of head scratching.

Problem

I want to show a "no data" legend when there's nothing to show for an asynchronously loaded list and a busy indicator whilst items are being sought/loaded and I'd like to keep the code required to do this to a minimum.

The standard way of showing "no data" is to provide the list's main layout with a widget/layout that has android:id="@id/android:empty" and one with android:id="@id/android:list". The OS decides which to use as necessary.

There's a fuller explanation here: ListActivity

This is all very well except that on startup, you'll see "no data" rather than, say, a progress bar and if we're downloading data or hitting a local DB, we really would rather see a busy indicator of some sort and only if there's nothing to list do we want to see "no data".

Solution

We need two layouts for the listview...

  • A "main" layout that handles list empty/not-empty.
  • An "item" layout for, surprise, each list item.

...and we'll need some code to handle widget visibility on completion of the asynchronous load.

Sample Main Layout

The trick, such as it is, is to make the android:empty "widget" in the layout a container rather than, as shown in every example that I managed to find using a well known search engine, a TextView or similar. Having done that, you can be as creative as you like within that container.

The simple layout below lets us centre the busy/empty indicator in the listview without any complicated positioning code.

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

    <!-- 
    The view to use when we've got data to display  .
    Acts as the container for individual list item views which are defined
    by their layout file. 
    -->
    <ListView 
        android:id="@id/android:list"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1"
        android:drawSelectorOnTop="false"/>

    <!-- 
    The view to use when the list is empty. 
    It has two widgets, a progress "bar" and the "no data" indicator.
    --> 
    <RelativeLayout 
        android:id="@id/android:empty"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
     	
      <!-- 
      The progress bar widget
      Set invisible if no items are available on completion of async. load.
      Defaults to visible so is automatically shown on start up/initial search/load.
      -->
      <ProgressBar 
          android:id="@+id/busy_BusyIndicator"
          android:layout_centerInParent="true"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          />

      <!-- 
      The "no data" indicator
      Set visible if no items are available on completion of async. load.
      Defaults to hidden so isn't shown until initial load/search is complete
      and has failed to find any items.
      -->
      <TextView 
          android:id="@+id/busy_EmptyIndicator"
          android:text="@string/hint_NoSavedItems"
          android:textIsSelectable="false"
          android:layout_centerInParent="true"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:visibility="invisible"
          />  
      		
     </RelativeLayout>

   </LinearLayout>

And in the list fragment (or activity) set the list's main layout in the onCreateView override.

Java
@Override
public View onCreateView(LayoutInflater inflater,
                         ViewGroup container,
                         Bundle savedInstanceState) {

  View view = inflater.inflate(R.layout.mainLayout, null);
  return view;
}

The layout for individual items is handled by the display adapter that serves your list fragment/activity specifically in the getView method.

Assuming the list is populated asynchronously and your async loader class is defined within the list fragment (or activity) then in your AsyncTask's onPostExecute it becomes very easy to show the appropriate widget.

Java
@Override
  protected void onPostExecute(ArrayList<{myType}> result) {

  // Do other list display setup / processing odds & ends...

  // If the list is empty hide the busy indicator and show the "no data"
  // indicator.
  View root = getView();
  if (root != null) {
    View busyIndicator = root.findViewById(R.id.busy_BusyIndicator);
    View emptyList = root.findViewById(R.id.busy_EmptyIndicator);
    if (busyIndicator != null && emptyList != null) {
      if (itemCount == 0) {					
        busyIndicator.setVisibility(View.INVISIBLE);
        emptyList.setVisibility(View.VISIBLE);
      }
      else {
        busyIndicator.setVisibility(View.VISIBLE);
        emptyList.setVisibility(View.INVISIBLE);
      }
    }
  }

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)