Adapting to Empty Views

Dave Smith
Dave Smith

Android has a pretty flexible framework for displaying data from a database, array, or other list structure using classes called Adapters. There are plenty of great tutorials in the Android docs and throughout the web on how to use adapters to create custom behavior for display this data, so I'm going to focus on its less-discussed couterpart; the AdapterView.

Adapter Basics

Android uses an Adapter class to bind data from a data structure (like a database Cursor or an array) to a view that is specially designed to dynamically display the data as it moves and changes...this is the AdapterView. There are many common subclasses of AdapterView (ListView, Spinner, Gallery, etc.), but they share the common function without any special overriding that I will discuss in detail today.

The Empty View

All AdapterView subclasses have a property that maintains a reference to a View object known as the empty view. The point of this view is to be displayed when the Adapter bound to our data tells the AdapterView that the data structure has no data. It does this either by just returning zero from getCount(), or through a more complex implementation of isEmpty(). Conceptually, it is then the role of the AdapterView to replace the view reserved for displaying content with this View, if it is not null.

Setting the Empty View for an AdapterView

You may call at any time AdapterView.setEmptyView() on an AdapterView instance to attach a view you would like to use as the empty view. You may also call AdapterView.getEmptyView() at any time to verify if an empty view has been set.

That's it...sort of.

There's a trick...

As of this writing, one of the things the Android documentation doesn't tell you is that the AdapterView simply manipulates the visibility properties of your content view and empty view based on the information it receives from the Adapter. After determining which view to display, it simply calls setVisibility(View.VISIBLE) on that view, and calls setVisibility(View.GONE) on the other. This is efficient and works just fine, but it requires that you create both views and insert them into a visible layout yourself. AdapterView will not do this legwork for you. You cannot assume that supplying an empty view to AdapterView means that it will insert that view in its place in the layout at runtime...

Example, please!

Let's examine the basic ListView example to get a grip on things. When you place a ListView in your layout per the SDK tutorials, it will look something like this:

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:id="@+id/list1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
  <TextView
    android:id="@+id/empty1"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:text="Nothing in the List!"
  />
</LinearLayout>

and the setup code in your activity would be like this:

ListView list;
TextView empty;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  empty = (TextView)findViewById(R.id.empty1);
  list = (ListView)findViewById(R.id.list1);

  list.setEmptyView(empty);

  //...code to create and set an adapter of your choice...
}

The key here is this works because both the ListView and its "empty view" are defined as children of the displayed root layout, so they are both laid out when the Activity is displayed. Because of this, the AdapterView method of simply manipulating visibility works perfectly. Let's modify the example now so that the layout XML looks like this:

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/layout1"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">
  <ListView
    android:id="@+id/list1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
</LinearLayout>

empty.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView
  android:id="@+id/empty1"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:gravity="center_horizontal"
  android:text="Nothing in the List!"
/>

...with the same setup code in our Activity. Now, the layout process is not handling the setup and inflation of our "empty view". When our list is empty, AdapterView will manipulate the visibility of the two view objects...so the content view will become invisible. However, with the empty view not attached to our layout, it will not show up anywhere on the screen (even though it has been set to View.VISIBLE)!

In order for this second example to work properly, you would have to modify the Java code as follows:

ListView list;
TextView empty;

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  empty = (TextView)findViewById(R.id.empty1);
  list = (ListView)findViewById(R.id.list1);

  //New code to add the empty view to the visible layout
  LinearLayout rootLayout = (LinearLayout)findViewById(R.id.layout1);
  rootLayout.addView(empty, 0);

  list.setEmptyView(empty);

  //...code to create and set an adapter of your choice...
}

Take-home reminder: AdapterView does nothing more for you than manage visibility between a content view and an empty view.

You are responsible to making sure those views are laid out in the appropriate locations prior to utilizing this functionality. You could also subclass the AdapterView you are using to handle it, but many AdapterView classes are not as straightforward as you might think. I would highly recommend reading the docs and the Android source thoroughly before doing so.

Looking Forward

Knowing this about AdapterView is actually a bit exciting because it means we have yet even more flexibility at our fingertips. This means that we are not forced to have the AdapterView content and empty views occupy the same space. We could define our content at the top of the screen (like an image gallery), and display the empty view somewhere else, or even on top of the entire screen (like a popup saying "nothing to see here, move along") restricting access to other UI elements in the view.