How to detect end of list when scrolling and trigger loading of more data from a WEB request into a ListView
If you are loading data from a WEB server and the list is huge, then the practical solution would be to load a certain amount and if the user scrolls to the end of the list, load some more and keep going until you load the full list. This is much more efficient programming. In this example we are loading 10 countries from a Java Servlet using HTTP request that gives us the data in JSON format from a MySQL database and then with the help of Scroll Listener we can check when we are at the end of the list and ready to make another request to load some more countries.
Source for Activity - AndroidListViewLoadMoreActivity.java
package com.as400samplecode;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.google.gson.Gson;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView.OnItemClickListener;
public class AndroidListViewLoadMoreActivity extends Activity {
ArrayList<Country> countryList;
MyCustomAdapter dataAdapter = null;
int start = 0;
int limit = 10;
boolean loadingMore = false;
View loadMoreView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView listView = (ListView) findViewById(R.id.listView1);
loadMoreView = ((LayoutInflater)this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.loadmore, null, false);
listView.addFooterView(loadMoreView);
//create an ArrayAdaptar from the String Array
countryList = new ArrayList<Country>();
dataAdapter = new MyCustomAdapter(this,
R.layout.country_info, countryList);
listView.setAdapter(dataAdapter);
//enables filtering for the contents of the given ListView
listView.setTextFilterEnabled(true);
listView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// When clicked, show a toast with the TextView text
Country country = (Country) parent.getItemAtPosition(position);
Toast.makeText(getApplicationContext(),
country.getCode(), Toast.LENGTH_SHORT).show();
}
});
listView.setOnScrollListener(new OnScrollListener(){
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
int lastInScreen = firstVisibleItem + visibleItemCount;
if((lastInScreen == totalItemCount) && !(loadingMore)){
String url = "http://10.0.2.2:8080/CountryWebService" +
"/CountryServlet";
grabURL(url);
}
}
});
String url = "http://10.0.2.2:8080/CountryWebService" + "/CountryServlet";
grabURL(url);
}
public void grabURL(String url) {
Log.v("Android Spinner JSON Data Activity", url);
new GrabURL().execute(url);
}
private class GrabURL extends AsyncTask<String, Void, String> {
private static final int REGISTRATION_TIMEOUT = 3 * 1000;
private static final int WAIT_TIMEOUT = 30 * 1000;
private final HttpClient httpclient = new DefaultHttpClient();
final HttpParams params = httpclient.getParams();
HttpResponse response;
private String content = null;
private boolean error = false;
private ProgressDialog dialog =
new ProgressDialog(AndroidListViewLoadMoreActivity.this);
protected void onPreExecute() {
dialog.setMessage("Getting your data... Please wait...");
dialog.show();
}
protected String doInBackground(String... urls) {
String URL = null;
loadingMore = true;
try {
URL = urls[0];
HttpConnectionParams.setConnectionTimeout(params, REGISTRATION_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, WAIT_TIMEOUT);
ConnManagerParams.setTimeout(params, WAIT_TIMEOUT);
HttpPost httpPost = new HttpPost(URL);
//add name value pair for the country code
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
nameValuePairs.add(new BasicNameValuePair("start",String.valueOf(start)));
nameValuePairs.add(new BasicNameValuePair("limit",String.valueOf(limit)));
httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
response = httpclient.execute(httpPost);
StatusLine statusLine = response.getStatusLine();
if(statusLine.getStatusCode() == HttpStatus.SC_OK){
ByteArrayOutputStream out = new ByteArrayOutputStream();
response.getEntity().writeTo(out);
out.close();
content = out.toString();
} else{
//Closes the connection.
Log.w("HTTP1:",statusLine.getReasonPhrase());
response.getEntity().getContent().close();
throw new IOException(statusLine.getReasonPhrase());
}
} catch (ClientProtocolException e) {
Log.w("HTTP2:",e );
content = e.getMessage();
error = true;
cancel(true);
} catch (IOException e) {
Log.w("HTTP3:",e );
content = e.getMessage();
error = true;
cancel(true);
}catch (Exception e) {
Log.w("HTTP4:",e );
content = e.getMessage();
error = true;
cancel(true);
}
return content;
}
protected void onCancelled() {
dialog.dismiss();
Toast toast = Toast.makeText(AndroidListViewLoadMoreActivity.this,
"Error connecting to Server", Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP, 25, 400);
toast.show();
}
protected void onPostExecute(String content) {
dialog.dismiss();
Toast toast;
if (error) {
toast = Toast.makeText(AndroidListViewLoadMoreActivity.this,
content, Toast.LENGTH_LONG);
toast.setGravity(Gravity.TOP, 25, 400);
toast.show();
} else {
displayCountryList(content);
}
}
}
private void displayCountryList(String response){
JSONObject responseObj = null;
try {
Gson gson = new Gson();
responseObj = new JSONObject(response);
JSONArray countryListObj = responseObj.getJSONArray("countryList");
//countryList = new ArrayList<Country>();
if(countryListObj.length() == 0){
ListView listView = (ListView) findViewById(R.id.listView1);
listView.removeFooterView(loadMoreView);
}
else {
for (int i=0; i<countryListObj.length(); i++){
start++;
//get the country information JSON object
String countryInfo = countryListObj.getJSONObject(i).toString();
//create java object from the JSON object
Country country = gson.fromJson(countryInfo, Country.class);
//add to country array list
countryList.add(country);
dataAdapter.add(country);
}
dataAdapter.notifyDataSetChanged();
loadingMore = false;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
private class MyCustomAdapter extends ArrayAdapter<Country> {
private ArrayList<Country> countryList;
public MyCustomAdapter(Context context, int textViewResourceId,
ArrayList<Country> countryList) {
super(context, textViewResourceId, countryList);
this.countryList = new ArrayList<Country>();
this.countryList.addAll(countryList);
}
private class ViewHolder {
TextView code;
TextView name;
TextView continent;
TextView region;
}
public void add(Country country){
Log.v("AddView", country.getCode());
this.countryList.add(country);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
Log.v("ConvertView", String.valueOf(position));
if (convertView == null) {
LayoutInflater vi = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
convertView = vi.inflate(R.layout.country_info, null);
holder = new ViewHolder();
holder.code = (TextView) convertView.findViewById(R.id.code);
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.continent = (TextView) convertView.findViewById(R.id.continent);
holder.region = (TextView) convertView.findViewById(R.id.region);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Country country = this.countryList.get(position);
holder.code.setText(country.getCode());
holder.name.setText(country.getName());
holder.continent.setText(country.getContinent());
holder.region.setText(country.getRegion());
return convertView;
}
}
}
Source for POJO - Country.java
package com.as400samplecode;
public class Country {
String code = null;
String name = null;
String continent = null;
String region = null;
Double lifeExpectancy = null;
Double gnp = null;
Double surfaceArea = null;
int population = 0;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getContinent() {
return continent;
}
public void setContinent(String continent) {
this.continent = continent;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public Double getLifeExpectancy() {
return lifeExpectancy;
}
public void setLifeExpectancy(Double lifeExpectancy) {
this.lifeExpectancy = lifeExpectancy;
}
public Double getGnp() {
return gnp;
}
public void setGnp(Double gnp) {
this.gnp = gnp;
}
public Double getSurfaceArea() {
return surfaceArea;
}
public void setSurfaceArea(Double surfaceArea) {
this.surfaceArea = surfaceArea;
}
public int getPopulation() {
return population;
}
public void setPopulation(int population) {
this.population = population;
}
}
Source for Main Screen Layout - main.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="fill_parent"
android:orientation="vertical">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:padding="10dp"
android:text="@string/some_text" android:textSize="20sp" />
<ListView android:id="@+id/listView1" android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
Source for List Layout - country_info.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="6dip" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="Code: "
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView1"
android:layout_below="@+id/textView1"
android:text="Name: "
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView2"
android:layout_below="@+id/textView2"
android:text="Continent: "
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/textView3"
android:layout_below="@+id/textView3"
android:text="Region: "
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="@+id/continent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView3"
android:layout_alignBottom="@+id/textView3"
android:layout_toRightOf="@+id/textView3"
android:text="TextView" />
<TextView
android:id="@+id/region"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/textView4"
android:layout_alignBottom="@+id/textView4"
android:layout_alignLeft="@+id/continent"
android:text="TextView" />
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/textView3"
android:layout_toRightOf="@+id/textView3"
android:text="TextView" />
<TextView
android:id="@+id/code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/textView2"
android:layout_alignLeft="@+id/name"
android:text="TextView" />
</RelativeLayout>
Source for Footer View Text - loadmore.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:gravity="center_horizontal"
android:padding="3dp"
android:layout_height="fill_parent">
<TextView
android:id="@id/android:empty"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:gravity="center"
android:padding="5dp"
android:text="Loading more data..."/>
</LinearLayout>
Source for application variables - strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">ListView Load More</string>
<string name="some_text">
Some North American Countries!
</string>
</resources>
Source for application manifest - AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.as400samplecode" android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light">
<activity android:name=".AndroidListViewLoadMoreActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Source for Java Servlet - CountryServlet.java
package com.as400samplecode;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.as400samplecode.util.Country;
import com.as400samplecode.util.CountryInformation;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public class CountryServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public CountryServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request,response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String start = request.getParameter("start");
String limit = request.getParameter("limit");
PrintWriter out = response.getWriter();
response.setContentType("text/html");
response.setHeader("Cache-control", "no-cache, no-store");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "-1");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET,POST");
response.setHeader("Access-Control-Allow-Headers", "Content-Type");
response.setHeader("Access-Control-Max-Age", "86400");
//get list of countries
CountryInformation countryInformation = new CountryInformation();
ArrayList<Country> countryList = countryInformation.getList(start, limit);
Gson gson = new Gson();
JsonArray arrayObj=new JsonArray();
for(int i=0;i<countryList.size();i++){
Country country = countryList.get(i);
JsonElement countryObj = gson.toJsonTree(country);
arrayObj.add(countryObj);
}
//create a new JSON object
JsonObject myObj = new JsonObject();
//add property as success
myObj.addProperty("success", true);
//add the countryList object
myObj.add("countryList", arrayObj);
//convert the JSON to string and send back
out.println(myObj.toString());
out.close();
}
}
Source for database utility functions - CountryInformation.java
package com.as400samplecode.util;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class CountryInformation {
Connection conn = null;
PreparedStatement stmt = null;
String sql = null;
public ArrayList<Country> getList(String start, String limit) {
ArrayList<Country> countryList = new ArrayList<Country>();
try {
Context ctx = (Context) new InitialContext().lookup("java:comp/env");
conn = ((DataSource) ctx.lookup("jdbc/mysql")).getConnection();
sql = "Select * from COUNTRY order by name,code LIMIT ?,?";
stmt = conn.prepareStatement(sql);
stmt.setInt(1,Integer.parseInt(start));
stmt.setInt(2,Integer.parseInt(limit));
ResultSet rs = stmt.executeQuery();
while(rs.next()){
Country country = new Country();
country.setCode(rs.getString("code").trim());
country.setName(rs.getString("name").trim());
country.setContinent(rs.getString("continent").trim());
country.setRegion(rs.getString("region").trim());
country.setLifeExpectancy(rs.getString("lifeExpectancy") ==
null ? new Double(0) :
Double.parseDouble(rs.getString("lifeExpectancy")
.trim()));
country.setGnp(rs.getString("gnp") == null ? new Double(0) :
Double.parseDouble(rs.getString("gnp").trim()));
country.setSurfaceArea(rs.getString("surfaceArea") ==
null ? new Double(0) :
Double.parseDouble(rs.getString("surfaceArea").trim()));
country.setPopulation(rs.getString("population") == null ? 0 :
Integer.parseInt(rs.getString("population").trim()));
countryList.add(country);
}
rs.close();
stmt.close();
stmt = null;
conn.close();
conn = null;
}
catch(Exception e){System.out.println(e);}
finally {
/*
* close any jdbc instances here that weren't
* explicitly closed during normal code path, so
* that we don't 'leak' resources...
*/
if (stmt != null) {
try {
stmt.close();
} catch (SQLException sqlex) {
// ignore -- as we can't do anything about it here
}
stmt = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException sqlex) {
// ignore -- as we can't do anything about it here
}
conn = null;
}
}
return countryList;
}
}
References
No comments:
Post a Comment
NO JUNK, Please try to keep this clean and related to the topic at hand.
Comments are for users to ask questions, collaborate or improve on existing.