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.
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);
}
}
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));
}
}
