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.
="1.0"="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">
<ListView
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:drawSelectorOnTop="false"/>
<RelativeLayout
android:id="@id/android:empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ProgressBar
android:id="@+id/busy_BusyIndicator"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<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.
@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.
@Override
protected void onPostExecute(ArrayList<{myType}> result) {
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);
}
}
}