March 14, 2012 12:36 PM

Android ListView with Load More Button

In this tutorial i am discussing about adding load more functionality to listview. It will be useful when you want to show data into paged format instead of loading huge amount of data into listview.

Download Code

Paged XML data

For this tutorial i am accessing an url which will give you paged XML by taking page number. Each page contains 20 results. You can access the xml by accessing url http://api.androidhive.info/list_paging/?page=1. This url accepts “page” (page number – 1, 2, 3 ..) parameter as a GET variable. The sample response will be

<menu>
  <item>
     <id>1</id>
     <name>sample list data 1</name>
  </item>
  <item>
     <id>2</id>
     <name>sample list data 2</name>
  </item>
  .
  .
  .
  <item>
     <id>20</id>
     <name>sample list data 20</name>
  </item>
<item>

You can prepare your own json or xml data with paging option. Example like displaying mysql data by taking page number.

Adding button to ListView at the bottom

You can add a view at the bottom of the listview by using addFooterView method. In this tutorial i am adding a button to the bottom of the listview.

// Getting listview from xml
ListView lv = (ListView) findViewById(R.id.list);

// Creating a button - Load More
Button btnLoadMore = new Button(this);
btnLoadMore.setText("Load More");

// Adding button to listview at footer
lv.addFooterView(btnLoadMore);

Running a Asynchronous Background Thread

We need a background thread while sending http request in order to get new data for listview. Following is a example of Async Background task.

private class YourBackgroundThreadName extends AsyncTask<Void, Void, Void> {

		@Override
		protected void onPreExecute() {
		   // Before starting background task
		   // Show Progress Dialog etc,.
		}

		protected Void doInBackground(Void... unused) {
			runOnUiThread(new Runnable() {
				public void run() {
                // Run actual background task
                // Like sending HTTP Request - Parsing data
				}
			});
			return (null);
		}

		protected void onPostExecute(Void unused) {
			// On completing background task
			// closing progress dialog etc,.
		}

Calling a background thread

// calling a background task
new YourBackgroundThreadName().execute();

Create new Project

1. Create a new project by going to File ⇒ New ⇒ Android Project and fill all the required details.
2. Open your main.xml and add a listview element in it.

<?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="fill_parent"
    android:orientation="vertical">

    <ListView
        android:id="@+id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

3. Create a new XML layout for single list row item under layouts folder.
Right Click on res ⇒ New ⇒ Android XML File and fill with following code. Name the xml file as list_item.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="wrap_content"
    android:orientation="horizontal">  

        <!-- List item name -->
        <TextView
            android:id="@+id/name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:textColor="#acacac"
            android:textStyle="bold"
            android:gravity="left"
            android:padding="10dip"
            android:textSize="16dp">
        </TextView>
</LinearLayout>

4. Create a new class and name it as ListViewAdapter.java and fill with following code. This class is required to fill the listview with parsed xml data. I just displayed single text view.

package com.example.androidhive;

import java.util.ArrayList;
import java.util.HashMap;

import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class ListViewAdapter extends BaseAdapter {

    private Activity activity;
    private ArrayList<HashMap<String, String>> data;
    private static LayoutInflater inflater=null;

    public ListViewAdapter(Activity a, ArrayList<HashMap<String, String>> d) {
        activity = a;
        data=d;
        inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public int getCount() {
        return data.size();
    }

    public Object getItem(int position) {
        return position;
    }

    public long getItemId(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        View vi=convertView;
        if(convertView==null)
            vi = inflater.inflate(R.layout.list_item, null);

        TextView name = (TextView)vi.findViewById(R.id.name);

        HashMap<String, String> item = new HashMap<String, String>();
        item = data.get(position);

        //Setting all values in listview
        name.setText(item.get("name"));
        return vi;
    }
}

5. Now open your MainActivity try the following code. In the following code i am just displaying data into listview by parsing XML. Finally i added a load more button to the bottom to listview

public class AndroidListViewWithLoadMoreButtonActivity extends Activity {

	// All variables
	XMLParser parser;
	Document doc;
	String xml;
	ListView lv;
	ListViewAdapter adapter;
	ArrayList<HashMap<String, String>> menuItems;
	ProgressDialog pDialog;

	private String URL = "http://api.androidhive.info/list_paging/?page=1";

	// XML node keys
	static final String KEY_ITEM = "item"; // parent node
	static final String KEY_ID = "id";
	static final String KEY_NAME = "name";

	// Flag for current page
	int current_page = 1;

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

		lv = (ListView) findViewById(R.id.list);

		menuItems = new ArrayList<HashMap<String, String>>();

		parser = new XMLParser();
		xml = parser.getXmlFromUrl(URL); // getting XML
		doc = parser.getDomElement(xml); // getting DOM element

		NodeList nl = doc.getElementsByTagName(KEY_ITEM);
		// looping through all item nodes <item>
		for (int i = 0; i < nl.getLength(); i++) {
			// creating new HashMap
			HashMap<String, String> map = new HashMap<String, String>();
			Element e = (Element) nl.item(i);
			// adding each child node to HashMap key => value
			map.put(KEY_ID, parser.getValue(e, KEY_ID)); // id not using any where
			map.put(KEY_NAME, parser.getValue(e, KEY_NAME));

			// adding HashList to ArrayList
			menuItems.add(map);
		}

		// LoadMore button
		Button btnLoadMore = new Button(this);
		btnLoadMore.setText("Load More");

		// Adding Load More button to lisview at bottom
		lv.addFooterView(btnLoadMore);

		// Getting adapter
		adapter = new ListViewAdapter(this, menuItems);
		lv.setAdapter(adapter);
	}
}
android listview with load more button

Working with Load More Button

Until now we displayed a simple listview with the first paged xml data. Now we need to add functionality to load more button. Add a click event listener to load more button and call a background thread which will append next paged data to listview.

Appending data to listview is nothing but appending data into adapter array. If you write your own Custom Adapter you need to call notifyDataSetChanged() on the adapter class in order to refresh list view. (For reference check this question: Dynamic ListView in Android app)

6. Add a click event listener to LoadMore button in your MainActivity Class and start a new background thread once it is clicked.

/**
* Listening to Load More button click event
* */
btnLoadMore.setOnClickListener(new View.OnClickListener() {
	@Override
	public void onClick(View arg0) {
		// Starting a new async task
		new loadMoreListView().execute();
	}
});

7. Now after closing onCreate Method write a background thread class like below. In the following code i am making a HTTP request with next page number. After getting the new paged data i am parsing it and loading into listview. Once the listview is appended with new data i am setting scroll position using setSelectionFromTop() method.

	/**
	 * Async Task that send a request to url
	 * Gets new list view data
	 * Appends to list view
	 * */
	private class loadMoreListView extends AsyncTask<Void, Void, Void> {

		@Override
		protected void onPreExecute() {
			// Showing progress dialog before sending http request
			pDialog = new ProgressDialog(
					AndroidListViewWithLoadMoreButtonActivity.this);
			pDialog.setMessage("Please wait..");
			pDialog.setIndeterminate(true);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		protected Void doInBackground(Void... unused) {
			runOnUiThread(new Runnable() {
				public void run() {
					// increment current page
					current_page += 1;

					// Next page request
					URL = "http://api.androidhive.info/list_paging/?page=" + current_page;

					xml = parser.getXmlFromUrl(URL); // getting XML
					doc = parser.getDomElement(xml); // getting DOM element

					NodeList nl = doc.getElementsByTagName(KEY_ITEM);
					// looping through all item nodes <item>
					for (int i = 0; i < nl.getLength(); i++) {
						// creating new HashMap
						HashMap<String, String> map = new HashMap<String, String>();
						Element e = (Element) nl.item(i);

						// adding each child node to HashMap key => value
						map.put(KEY_ID, parser.getValue(e, KEY_ID));
						map.put(KEY_NAME, parser.getValue(e, KEY_NAME));

						// adding HashList to ArrayList
						menuItems.add(map);
					}

					// get listview current position - used to maintain scroll position
					int currentPosition = lv.getFirstVisiblePosition();

					// Appending new data to menuItems ArrayList
					adapter = new ListViewAdapter(
							AndroidListViewWithLoadMoreButtonActivity.this,
							menuItems);

					// Setting new scroll position
					lv.setSelectionFromTop(currentPosition + 1, 0);
				}
			});
			return (null);
		}		

		protected void onPostExecute(Void unused) {
			// closing progress dialog
			pDialog.dismiss();
		}
	}

Final Code

The final code of the MainActivity class is

package com.example.androidhive;

import java.util.ArrayList;
import java.util.HashMap;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

public class AndroidListViewWithLoadMoreButtonActivity extends Activity {

	// All variables
	XMLParser parser;
	Document doc;
	String xml;
	ListView lv;
	ListViewAdapter adapter;
	ArrayList<HashMap<String, String>> menuItems;
	ProgressDialog pDialog;

	private String URL = "http://api.androidhive.info/list_paging/?page=1";

	// XML node keys
	static final String KEY_ITEM = "item"; // parent node
	static final String KEY_ID = "id";
	static final String KEY_NAME = "name";

	// Flag for current page
	int current_page = 1;

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

		lv = (ListView) findViewById(R.id.list);

		menuItems = new ArrayList<HashMap<String, String>>();

		parser = new XMLParser();
		xml = parser.getXmlFromUrl(URL); // getting XML
		doc = parser.getDomElement(xml); // getting DOM element

		NodeList nl = doc.getElementsByTagName(KEY_ITEM);
		// looping through all item nodes <item>
		for (int i = 0; i < nl.getLength(); i++) {
			// creating new HashMap
			HashMap<String, String> map = new HashMap<String, String>();
			Element e = (Element) nl.item(i);
			// adding each child node to HashMap key => value
			map.put(KEY_ID, parser.getValue(e, KEY_ID)); // id not using any where
			map.put(KEY_NAME, parser.getValue(e, KEY_NAME));

			// adding HashList to ArrayList
			menuItems.add(map);
		}

		// LoadMore button
		Button btnLoadMore = new Button(this);
		btnLoadMore.setText("Load More");

		// Adding Load More button to lisview at bottom
		lv.addFooterView(btnLoadMore);

		// Getting adapter
		adapter = new ListViewAdapter(this, menuItems);
		lv.setAdapter(adapter);

		/**
		 * Listening to Load More button click event
		 * */
		btnLoadMore.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View arg0) {
				// Starting a new async task
				new loadMoreListView().execute();
			}
		});

		/**
		 * Listening to listview single row selected
		 * **/
		lv.setOnItemClickListener(new OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				// getting values from selected ListItem
				String name = ((TextView) view.findViewById(R.id.name))
						.getText().toString();

				// Starting new intent
				Intent in = new Intent(getApplicationContext(),
						SingleMenuItemActivity.class);
				in.putExtra(KEY_NAME, name);
				startActivity(in);
			}
		});
	}

	/**
	 * Async Task that send a request to url
	 * Gets new list view data
	 * Appends to list view
	 * */
	private class loadMoreListView extends AsyncTask<Void, Void, Void> {

		@Override
		protected void onPreExecute() {
			// Showing progress dialog before sending http request
			pDialog = new ProgressDialog(
					AndroidListViewWithLoadMoreButtonActivity.this);
			pDialog.setMessage("Please wait..");
			pDialog.setIndeterminate(true);
			pDialog.setCancelable(false);
			pDialog.show();
		}

		protected Void doInBackground(Void... unused) {
			runOnUiThread(new Runnable() {
				public void run() {
					// increment current page
					current_page += 1;

					// Next page request
					URL = "http://api.androidhive.info/list_paging/?page=" + current_page;

					xml = parser.getXmlFromUrl(URL); // getting XML
					doc = parser.getDomElement(xml); // getting DOM element

					NodeList nl = doc.getElementsByTagName(KEY_ITEM);
					// looping through all item nodes <item>
					for (int i = 0; i < nl.getLength(); i++) {
						// creating new HashMap
						HashMap<String, String> map = new HashMap<String, String>();
						Element e = (Element) nl.item(i);

						// adding each child node to HashMap key => value
						map.put(KEY_ID, parser.getValue(e, KEY_ID));
						map.put(KEY_NAME, parser.getValue(e, KEY_NAME));

						// adding HashList to ArrayList
						menuItems.add(map);
					}

					// get listview current position - used to maintain scroll position
					int currentPosition = lv.getFirstVisiblePosition();

					// Appending new data to menuItems ArrayList
					adapter = new ListViewAdapter(
							AndroidListViewWithLoadMoreButtonActivity.this,
							menuItems);
					lv.setAdapter(adapter);
					// Setting new scroll position
					lv.setSelectionFromTop(currentPosition + 1, 0);

				}
			});

			return (null);
		}

		protected void onPostExecute(Void unused) {
			// closing progress dialog
			pDialog.dismiss();
		}
	}
}

Other Classes needed in this project

XMLParser.java – needed to parse xml data

package com.example.androidhive;

import java.io.IOException;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import android.util.Log;

public class XMLParser {

	// constructor
	public XMLParser() {

	}

	/**
	 * Getting XML from URL making HTTP request
	 * @param url string
	 * */
	public String getXmlFromUrl(String url) {
		String xml = null;

		try {
			// defaultHttpClient
			DefaultHttpClient httpClient = new DefaultHttpClient();
			HttpPost httpPost = new HttpPost(url);

			HttpResponse httpResponse = httpClient.execute(httpPost);
			HttpEntity httpEntity = httpResponse.getEntity();
			xml = EntityUtils.toString(httpEntity);

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		// return XML
		return xml;
	}

	/**
	 * Getting XML DOM element
	 * @param XML string
	 * */
	public Document getDomElement(String xml){
		Document doc = null;
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		try {

			DocumentBuilder db = dbf.newDocumentBuilder();

			InputSource is = new InputSource();
		        is.setCharacterStream(new StringReader(xml));
		        doc = db.parse(is); 

			} catch (ParserConfigurationException e) {
				Log.e("Error: ", e.getMessage());
				return null;
			} catch (SAXException e) {
				Log.e("Error: ", e.getMessage());
	            return null;
			} catch (IOException e) {
				Log.e("Error: ", e.getMessage());
				return null;
			}

	        return doc;
	}

	/** Getting node value
	  * @param elem element
	  */
	 public final String getElementValue( Node elem ) {
	     Node child;
	     if( elem != null){
	         if (elem.hasChildNodes()){
	             for( child = elem.getFirstChild(); child != null; child = child.getNextSibling() ){
	                 if( child.getNodeType() == Node.TEXT_NODE  ){
	                     return child.getNodeValue();
	                 }
	             }
	         }
	     }
	     return "";
	 }

	 /**
	  * Getting node value
	  * @param Element node
	  * @param key string
	  * */
	 public String getValue(Element item, String str) {
			NodeList n = item.getElementsByTagName(str);
			return this.getElementValue(n.item(0));
		}
}
This image is for thumbnail purpose.
android listview with load more button

(If you find any error either in code or content please help me in improvising the content)

THANK YOU
Oops! We are unable to process your request at this moment. Please try again.