Android Series: Custom ListView items and adapters

Posted by admin | Posted in Mobile Development | Posted on 20-05-2009

80

This is a short tutorial on how to populate your android list view, with data downloaded from the internet or other sources, using ArrayAdapter. ListView items view is declared in a separate XML file and displayed using custom adapter class.
First things first, so go ahead and create a new project using Eclipse equipped with ADT plugin.
The project described below assumes you have a list of objects created, this can be either downloaded from the internet as XML and parsed to create ArrayList of your custom objects or anything you imagine. I will not go into details on this tutorial how to create such an ArrayList but your imagination is the limit. Parsing XML downloaded from the net will be covered in the next tutorial coming up soon.


Click File -> New -> Project and select the ‘Android Project’ wizard:

android11

Click next and fill out the next screen with the following values:

android21

Once you have filled out all the necessary data you can click finish.
Your new project has just been created. Now lets modify it a bit to display our custom made list.
Open up SoftwarePassionView.java in the eclipse editor and change the class file to the following:

1. Define necessary member variables we will use later in our class

private ProgressDialog m_ProgressDialog = null;
    private ArrayList<Order> m_orders = null;
    private OrderAdapter m_adapter;
    private Runnable viewOrders;

m_ProgressDialog is a small pop up displaying information that your data is being downloaded or retrieved other way.
m_orders is an ArrayList which will hold our data downloaded from the internet or acquired other way
m_adapter is our custom class extending ArrayAdapter
viewOrders is a runnable for downloading data from the internet in a separate thread

To import whatever you can at this point click Ctrl+Shit+O, some classes like Order or OrderAdapter are not created yet but don’t worry we will come to that point soon.
Another important note at this point is that your SoftwarePassoinView should extend ListActivity instead of simple Activity.
Your class should look more or less something like this now:

package com.softberries.lve;

import java.util.ArrayList;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.os.Bundle;

public class SoftwarePassionView extends ListActivity{
   
    private ProgressDialog m_ProgressDialog = null;
    private ArrayList<Order> m_orders = null;
    private OrderAdapter m_adapter;
    private Runnable viewOrders;
   
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Now lets create our simple Order class holding single order.
Right click on the project and and select ‘New’ -> ‘Class’, name it order and open it up in the editor.
The source code for our orders looks like this:

package com.softberries.lve;

public class Order {
   
    private String orderName;
    private String orderStatus;
   
    public String getOrderName() {
        return orderName;
    }
    public void setOrderName(String orderName) {
        this.orderName = orderName;
    }
    public String getOrderStatus() {
        return orderStatus;
    }
    public void setOrderStatus(String orderStatus) {
        this.orderStatus = orderStatus;
    }
}

The Order class is very simple and contains only 2 strings with getters and setter generated for them
Now lets change our main.xml file to hold our custom list items:

<?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/main_no_items"/>
</LinearLayout>

This layout will display our list items if any and if the list is empty it will display ‘No orders to display’ string defined in string.xml resource file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello World, SoftwarePassionView!</string>
    <string name="app_name">Software Passion</string>
    <string name="main_no_items">No orders to display</string>
</resources>



Our list item (single row on the list) have a custom layout as well, defined in row.xml file:

<?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="6dip">
    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="6dip"
        android:src="@drawable/icon" />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">
        <TextView
            android:id="@+id/toptext"
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:gravity="center_vertical"
        />
        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
            android:id="@+id/bottomtext"
            android:singleLine="true"
            android:ellipsize="marquee"
        />
    </LinearLayout>
</LinearLayout>

Single row example has been borrowed from the romain Guy website here

Ok, so we have all our layouts defined in the res folder under layout. Now its time to go back to our code and create our custom OrderAdapter class which will manage our list of orders:

private class OrderAdapter extends ArrayAdapter<Order> {

        private ArrayList<Order> items;

        public OrderAdapter(Context context, int textViewResourceId, ArrayList<Order> items) {
                super(context, textViewResourceId, items);
                this.items = items;
        }

        @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.row, null);
                }
                Order o = items.get(position);
                if (o != null) {
                        TextView tt = (TextView) v.findViewById(R.id.toptext);
                        TextView bt = (TextView) v.findViewById(R.id.bottomtext);
                        if (tt != null) {
                              tt.setText("Name: "+o.getOrderName());                            }
                        if(bt != null){
                              bt.setText("Status: "+ o.getOrderStatus());
                        }
                }
                return v;
        }
}

This is a private class and should be added to our SoftwarePassionView. This is extended ListAdapter which inside overriden getView method returns our row with assigned string values to the textfields defined in row.xml.

A huge part of our application is already done. Now we have to add some modifications to the onCreate method to initialize everything properly and add a method retrieving our orders from somewhere, lets start with the latter:

private void getOrders(){
          try{
              m_orders = new ArrayList<Order>();
              Order o1 = new Order();
              o1.setOrderName("SF services");
              o1.setOrderStatus("Pending");
              Order o2 = new Order();
              o2.setOrderName("SF Advertisement");
              o2.setOrderStatus("Completed");
              m_orders.add(o1);
              m_orders.add(o2);
                 Thread.sleep(2000);
              Log.i("ARRAY", ""+ m_orders.size());
            } catch (Exception e) {
              Log.e("BACKGROUND_PROC", e.getMessage());
            }
            runOnUiThread(returnRes);
        }



Instead of creating our simple orders in the method above you could of course download them from somewhere and assign the result to the m_orders array list. The method runOnUIThread is a utility method for running tasks back on the main UI thread after the job is done on the separate thread created for long running tasks. We will call our getOrders method from a separate thread.

The returnRes runnable adds newly retrieved Order object to our custom Adapter and notifies it of the data change:

private Runnable returnRes = new Runnable() {

            @Override
            public void run() {
                if(m_orders != null && m_orders.size() > 0){
                    m_adapter.notifyDataSetChanged();
                    for(int i=0;i<m_orders.size();i++)
                    m_adapter.add(m_orders.get(i));
                }
                m_ProgressDialog.dismiss();
                m_adapter.notifyDataSetChanged();
            }
          };

Now lets move to our overriden onCreate method. We will initialize here all the member variables as well as start a new thread retrieving our orders:

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        m_orders = new ArrayList<Order>();
        this.m_adapter = new OrderAdapter(this, R.layout.row, m_orders);
                setListAdapter(this.m_adapter);
       
        viewOrders = new Runnable(){
            @Override
            public void run() {
                getOrders();
            }
        };
    Thread thread =  new Thread(null, viewOrders, "MagentoBackground");
        thread.start();
        m_ProgressDialog = ProgressDialog.show(SoftwarePassionView.this,    
              "Please wait...", "Retrieving data ...", true);
    }

After initialization, we start new thread using the viewOrders runnable and show the progress dialog which we close once the orders are retrieved.
Now you should be able to run your application. After the application starts it spawns new thread and displays the loader:

screen_load screen1

And thats it. You can add an Item Click Listener to your list to start new activities etc.
Full source code for the SoftwarePassionView below:

package com.softberries.lve;

import java.util.ArrayList;
import android.app.ListActivity;
import android.app.ProgressDialog;
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 SoftwarePassionView extends ListActivity{
   
    private ProgressDialog m_ProgressDialog = null;
    private ArrayList<Order> m_orders = null;
    private OrderAdapter m_adapter;
    private Runnable viewOrders;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        m_orders = new ArrayList<Order>();
        this.m_adapter = new OrderAdapter(this, R.layout.row, m_orders);
        setListAdapter(this.m_adapter);
       
        viewOrders = new Runnable(){
            @Override
            public void run() {
                getOrders();
            }
        };
        Thread thread =  new Thread(null, viewOrders, "MagentoBackground");
        thread.start();
        m_ProgressDialog = ProgressDialog.show(SoftwarePassionView.this,    
              "Please wait...", "Retrieving data ...", true);
    }
    private Runnable returnRes = new Runnable() {

        @Override
        public void run() {
            if(m_orders != null && m_orders.size() > 0){
                m_adapter.notifyDataSetChanged();
                for(int i=0;i<m_orders.size();i++)
                m_adapter.add(m_orders.get(i));
            }
            m_ProgressDialog.dismiss();
            m_adapter.notifyDataSetChanged();
        }
    };
    private void getOrders(){
          try{
              m_orders = new ArrayList<Order>();
              Order o1 = new Order();
              o1.setOrderName("SF services");
              o1.setOrderStatus("Pending");
              Order o2 = new Order();
              o2.setOrderName("SF Advertisement");
              o2.setOrderStatus("Completed");
              m_orders.add(o1);
              m_orders.add(o2);
              Thread.sleep(5000);
              Log.i("ARRAY", ""+ m_orders.size());
            } catch (Exception e) {
              Log.e("BACKGROUND_PROC", e.getMessage());
            }
            runOnUiThread(returnRes);
        }
    private class OrderAdapter extends ArrayAdapter<Order> {

        private ArrayList<Order> items;

        public OrderAdapter(Context context, int textViewResourceId, ArrayList<Order> items) {
                super(context, textViewResourceId, items);
                this.items = items;
        }
        @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.row, null);
                }
                Order o = items.get(position);
                if (o != null) {
                        TextView tt = (TextView) v.findViewById(R.id.toptext);
                        TextView bt = (TextView) v.findViewById(R.id.bottomtext);
                        if (tt != null) {
                              tt.setText("Name: "+o.getOrderName());                            }
                        if(bt != null){
                              bt.setText("Status: "+ o.getOrderStatus());
                        }
                }
                return v;
        }
}
}



Enjoy!

StumbleuponDelicious

Comments (80)

Great tutorial !

I think this is the first tutorial for ListViews, that acutally works and comes straight to the point.

Thanks :-)

Great!

But I have a doubt… I dont’t understand why it is necessary to add items to the arrayAdapter if those items have allready been added to the array which is linked to. Which is the sense of the arrayAdapter then? Can anybody shed light on these lines?

m_adapter.notifyDataSetChanged(); //why is it done before updating it?
for(int i=0;i<m_orders.size();i++)
m_adapter.add(m_orders.get(i));

Thanks in advance!

Yours,

Charl!e

True, I tried that this way as well, but aparently adding items to the ArrayList connected with the adpater and calling notifyDataSetChanged() doesn’t work. ArrayAdapter has its own method add().

Thank you for this tutorial. It was very well written, and it was easily adaptable for other projects. Good job.

Dan

Where in the code would you add the listener so that you could perform some action once an item on the list has been selected?

Thanks

You have to override onItemClick method to handle list items selection

Very great Tutorial!
My Question:
I have some TabHosts with ListViews in my layout xml.
For this (i think!?) i must extend Activity and not ListActivity in my main activity for this view.
Is this a Problem?
What kind of changes i have to do? (ListActivity to Activity )

Thanks

Esta buenísimo, muchas gracias…!

Hi. It’s a great tutorial but there is one thing i didn’t understand.

It seems to me that the the textview you are displaying when there are no elements automatically dissapears when there are some elements.

I don’t understand.

I call setVisibility(View.GONE) on my textbox once i have the list elements.

hope you can explain.

Hi,

I used this tutorial to show custom list view. This works fine.
Except focus is not seen on listItem. Even I clicked D-pad up,down keys focus its not showing focus. Do I need any extra things to use focus?

check out for better performance…. it’s just a general example

public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;

if(convertView == null) {
convertView = mInflater.inflate(R.layout.item, null);

holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);

convertView.setTag(holder);
}

holder.test.setText(DATA[position]);
holder.icon.setImageBitmap((position & 1) == 1 ? mIcon1 : mIcon2);

return convertView;
}

This is exactly the kind of tutorial I’ve been searching for nonstop for days now. Thank you!

Man, you’ve just made my day with this tutorial. Excellent.
Thanks a lot.

thx, you save me!

Can you please let me know how to get the selected item in the list for the above example?

Great tutorial..thanks!!!

First of all thanks for the great tutorial!! I got this running, though I had to add Looper.prepare() to the viewOrders run() method… Like many others I am a little confused on how to take this view (is it a view or activity??) and add it to a tab. My application uses 5 tabs that load data from 5 different sources, but displays them all in a list view… Any help would be fantastic!

Excellent tutorial. Thank you

Thank you very much for this excellent tut!

Very nice tutorial. Thank you.

Would be nice to see a similar one explaining ExpandableLists the same straight way.

Thanks alot for the tutorial

Thanks for this code, it is nice code to implement in program. But can you provide the code to click on listView, we edit on that list

Thanks so much for this! Great explanation of ListView.

Thanks Dude!

hey its too great tut…
thanx dude…
well m having one problem same as andy problem
what changes i have to made if my class extending activity class not list activity…

My layout contents other element too…so i cannt extend ListActivity..

Can any one help me…?
thanx in advance

Hey,,its excellent

But I wanna create tabs at the bottom of the screen,,how to do that,,,Is it possible?,,,

Thanks,,

Well,

I have had no luck with getting a custom ListView to work.

This tutorial runs, but all I see is “No orders to display”.

Is this really working for anyone else? Did you have to make some tweaks?

joe

Hello Joe,
you would have to provide more details what exactly is happening, simply saying what you can see on the screen doesn’t help me to help you. Try to debug your app and if you still have a problem let me know.

Not sure, I will have a look at the weekend at this tutorial again, if I’ll find something I’ll let you know.

Thanks for great tutorial!

Little improvement IHO:

Inside OrderAdapter,

It doesn’t seem to be necessary to have this object variable
private ArrayList items;

as it should be available in the parent class.

And use
getItem(position) ( or super.getItem(position))

instead of
items.get(..)

wow it works perfrect! thank you!

Combined this with a webservices (ksoap) and it works perfectly. Also nice details of the ProgressDialog. SWEET :)

Thank you.

I try to run this but after the retrieving dialog box runs it shuts down and displays that it has shut down unexpectedly. Do you know what may be the cause of this? I am running on google api 1.5 sdk 2.

Try to debug it, at least by placing the Log(“”,”") statements in your code. Otherwise I won’t be able to help you.

Thanks for great tutorial!

I tried running this application..
Although the icons loaded the text was nowhere to be seen. And the vertical space for each item is very wide. Did I forget to set something?

hmnn, interesting. Please double check your layout xml files and the string variables they contain.

Hi, Great tutorial.
But i am having problems with this line

for(int i = 0; i < data2.size(); i++){
rAdapter.add(data2.get(i));
}

(i changed the variable names)

Basically it loops forever. the arraylist seems to increase in size and keeps going

thats pretty basic, please double check you code or try to use debugger.

Well i scratched my head for a while and removed that code. this works perfectly without the loop

private Runnable returnData = new Runnable(){

@Override
public void run() {
if(listViewData != null && listViewData.size() > 0){
rAdapter.notifyDataSetChanged();
}
rProgressDialog.dismiss();
rAdapter.notifyDataSetChanged();
}
};

In your example how would you refresh the list view?

HI, great tutorial! Thanks… can you post a .RAR or .ZIP with the complete project to know exaclty where to put each part off the code?

Thanks again!!

Hi Daniel,

I encountered the same “infinite for loop” problem with you where the arraylist seemed to increase in size.

under my equivalent of “getOrders()” i made a http connection, received an xml response and parsed it into an arraylist of objects.

i followed your fix and removed the loop. seems to work fine, and the listview displays all the correct items, but i was wondering if there are any drawbacks to this.

any updates?

thanks
Shaun

Hi
the tutorials is gr8, it works absolutely fine.
But can someone tell me how to add filter to this
what i mean is:
[if i write A in a testbox all the names starting with A should come up]

i need this very urgently so plz….

Thanks in adv

sorry Pablo, don’t have the sources any more.

Great…. you goth my respect dude…

Hi,

this was very useful for me. However I think it is strange that you have to add each item and copy them from one list to the other, as mentioned above. Does anybody have a solution to this problem?

Regards
Johan

Many thanks, It’s helpful

Great tutorial.

Did you ever get around to doing a tutorial on a more-robust getOrders() method? Maybe one that reads from XML or pulls via http?

That would be awesome if you did.

Write a comment