Saturday, January 22, 2011

Intro to ListActivity 101

A very common Activity in many apps is the List. The List is commonly used for Menus and Navigations and can also be used to display data, ala a table.

Mastering the ListActivity and ListView early should be a goal.

In my example, MyDemo, I will show how I created a ListActivity to display "Dodads".

Before I created the ListActivity I created a simple Dodad class with two properties, name and Description.

Dodad.java

package com.mydemo.dodad;

public class Dodad {

private String m_name;
private String m_description;

public String getName() {
return m_name;
}
public void setName(String name) {
this.m_name = name;
}
public String getDescription() {
return m_description;
}
public void setDescription(String description) {
this.m_description = description;
}
}

Next I created my ListActivity class, in this case I just called it Welcome since it's the main activity for my app.

In it's bare bones it should look like this.

Welcome.java

package com.mydemo;

import android.app.ListActivity;
import android.os.Bundle;
import android.util.Log;

public class Welcome extends ListActivity {

private static final String TAG = "MyDemo";

public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "Welcome onCreate() start");
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

Log.d(TAG, "Welcome onCreate() done");
}
}

Notice that I use the android.util.log class to mark when the onCreate method is executed, both in and out. This is a good habit as far as I'm concerned. Debugging your application via the log is difficult if you don't log effectively. Learn to use the log utility and it's different levels. You can also see I created a TAG for my application name, this makes finding my applications log statements easier. Especially since the DDMS log allows you to set filters based upon this TAG.

Now is the time to create the layout for this ListActivity. I created a very simple, probably as simple as it gets, layout.

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView
android:id="@+id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
<TextView
android:id="@+id/android:empty"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:text="@string/no_dodads"/>
</LinearLayout>

I used a LinearLayout to hold the ListView and a TextView. Thing to note here is the id used for the ListView and the TextView. The ListActivity looks for a ListView in the layout with an id of 'android:list'. If it doesn't find it I've found that it's not happy. Also the TextView has an id of 'android:empty' which allows it to be displayed in lieu of ListView. This means it's designed to tell the user there is no data for the list. In this case I also added a string to the string.xml that states as much.

Here's where things take the next step. Since I want to display the Dodad Name and Description in each List item I'll need to create another layout that defines the List item or row as I tend to call it. I'll also need to create an Adapter for the ListView, in this case an ArrayAdapter.

First let's look at the layout needed to display two lines of text within each ListView item.

dodad_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:padding="5dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="fill_parent">
<TextView
android:id="@+id/textDodadLine1"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:textSize="20sp"/>
<TextView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/textDodadLine2"
android:singleLine="true"
android:textSize="14sp"/>
</LinearLayout>
</LinearLayout>

This layout was simple enough, again at the root is a LinearLayout with a vertical orientation. The orientation is vertical because I want the two lines of text to be on top of each other. Within the LinearLayout you see two TextViews, one for the top line and one for the bottom line. I set their text size so that the top row is larger than the bottom. That's just the look I'm going for, feel free to play around in here to make the ListView item appear however you desire.

Now we go back to the Java code in the ListActivity class. I needed to add an Adapter to link the Dodad class to the ListView and to apply the layout above to each ListView item.

You can see the class I added below.

private class DodadAdapter extends ArrayAdapter
{
private ArrayList dodads;

public DodadAdapter(Context context, int textViewResourceId, ArrayList dodads)
{
super(context, textViewResourceId, dodads);
this.dodads = dodads;
}

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;

if (v == null)
{
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.dodad_row, null);
}

Dodad dodad = dodads.get(position);
if (dodad != null)
{
TextView line1 = (TextView) v.findViewById(R.id.textDodadLine1);
if (line1 != null)
{
line1.setText(dodad.getName());
}

TextView line2 = (TextView) v.findViewById(R.id.textDodadLine2);
if(line2 != null)
{
line2.setText(dodad.getDescription());
}
}

return v;
}
}

Now feel free to just copy that code and know that it just works but I suggest to take the time to dissect it and understand what it's doing, at least at a basic level. First note that the class extends ArrayAdapter which we are feeding in our Dodad class. Second thing to note is the override of the getView method. This allows us to apply our custom layout to each ListView item or as the API calls it 'inflate'. it's in this method where the data is extracted from the Dodad object. So if we added an additional property to the Dodad class and we wanted to display it in the ListView as well we would need to come back here and make that happen.

We are now so close to running our ListView app and seeing what we've accomplished.

We now need to write some code to initialize our Dodad data. In this example I simply wrote a method to do this. In the real world this data would most likely come from resources or a server/database. But I wanted to focus on the ListView so it's just hard coded for now.

private void getDodads()
{
Log.d(TAG, "Welcome getDodads() start");
try
{
// Just some dummy data for the purpose of the sample
// This code could easily fetch data from a server/database.

m_dodads = new ArrayList();
Dodad dodad1 = new Dodad();
dodad1.setName("Dodad Number 1");
dodad1.setDescription("Cool little Dodad");
Dodad dodad2 = new Dodad();
dodad2.setName("Dodad Number 2");
dodad2.setDescription("The everyday Dodad");
Dodad dodad3 = new Dodad();
dodad3.setName("Dodad Number 3");
dodad3.setDescription("Essential do everything Dodad");
m_dodads.add(dodad1);
m_dodads.add(dodad2);
m_dodads.add(dodad3);

Log.d(TAG, "Welcome getDodads() returned (" + m_dodads.size() + ") dodads");
}
catch (Exception e)
{
Log.e(TAG, e.getMessage());
}

Log.d(TAG, "Welcome getDodads() done");
}

Really close to running the ListActivity app for the first time. We now only need to go back to our onCreate method of our ListActivity and make the call to our getDodads method and to set the ArrayAdapter.

Here is the onCreate method after these last modifications.

public void onCreate(Bundle savedInstanceState)
{
Log.d(TAG, "Welcome onCreate() start");
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

m_dodads = new ArrayList();
this.m_adapter = new DodadAdapter(this, R.layout.dodad_row, m_dodads);
setListAdapter(this.m_adapter);

getDodads();

if(m_dodads != null && m_dodads.size() > 0)
{
for(int i=0; i < m_dodads.size(); i++) { m_adapter.add(m_dodads.get(i)); } m_adapter.notifyDataSetChanged(); } m_adapter.notifyDataSetChanged(); Log.d(TAG, "Welcome onCreate() done");
}

Now we can run our app! And this is what we see.



Here is the entire source for the ListActivity class.

Welcome.java

package com.mydemo;

import java.util.ArrayList;

import com.mydemo.dodad.Dodad;

import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class Welcome extends ListActivity {

private static final String TAG = "MyDemo";

private ArrayList m_dodads = null;
private DodadAdapter m_adapter;

public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "Welcome onCreate() start");
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

m_dodads = new ArrayList();
this.m_adapter = new DodadAdapter(this, R.layout.dodad_row, m_dodads);
setListAdapter(this.m_adapter);

getDodads();

if(m_dodads != null && m_dodads.size() > 0)
{
for(int i=0; i < m_dodads.size(); i++) { m_adapter.add(m_dodads.get(i)); } m_adapter.notifyDataSetChanged(); } m_adapter.notifyDataSetChanged(); Log.d(TAG, "Welcome onCreate() done"); } private void getDodads() { Log.d(TAG, "Welcome getDodads() start"); try { // Just some dummy data for the purpose of the sample // This code could easily fetch data from a server/database. m_dodads = new ArrayList();
Dodad dodad1 = new Dodad();
dodad1.setName("Dodad Number 1");
dodad1.setDescription("Cool little Dodad");
Dodad dodad2 = new Dodad();
dodad2.setName("Dodad Number 2");
dodad2.setDescription("The everyday Dodad");
Dodad dodad3 = new Dodad();
dodad3.setName("Dodad Number 3");
dodad3.setDescription("Essential do everything Dodad");
m_dodads.add(dodad1);
m_dodads.add(dodad2);
m_dodads.add(dodad3);

Log.d(TAG, "Welcome getDodads() returned (" + m_dodads.size() + ") dodads");
}
catch (Exception e)
{
Log.e(TAG, e.getMessage());
}

Log.d(TAG, "Welcome getDodads() done");
}

private class DodadAdapter extends ArrayAdapter
{

private ArrayList dodads;

public DodadAdapter(Context context, int textViewResourceId, ArrayList dodads)
{
super(context, textViewResourceId, dodads);
this.dodads = dodads;
}

@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View v = convertView;

if (v == null)
{
LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.dodad_row, null);
}

Dodad dodad = dodads.get(position);
if (dodad != null)
{
TextView line1 = (TextView) v.findViewById(R.id.textDodadLine1);
if (line1 != null)
{
line1.setText(dodad.getName());
}

TextView line2 = (TextView) v.findViewById(R.id.textDodadLine2);
if(line2 != null)
{
line2.setText(dodad.getDescription());
}
}

return v;
}
}
}

2 comments:

  1. Can you provide the entire package for this tutorial and the next one in a zip file please? I went through your steps but had several errors in the code and was not able to clean them out in order to run the app and get the result in your screenshot. Thanks,

    Scott

    ReplyDelete
  2. ...Let me add that this is by far the best tutorial for this issue in Android development. Kudos!!

    ReplyDelete