We need to access
data from web in more than 90% of our Android applications. We use either of Threads,
AsyncTasks, IntentServices etc to access data from webservices. Each of the
ways has its own pros&cons. I’m going to show you my way to do so without
using any of above ways. And as far as I know, I can say that this is THE BEST way to
access data from web if you can convert your response data to a cursor object.
Assume you
need to access the below webservice and display user name and id in a ListView as
in screenshot. https://api.github.com/gists/public
MainActivity.java
:
import
android.database.Cursor;
import
android.os.Bundle;
import
android.support.v4.app.FragmentActivity;
import
android.support.v4.app.LoaderManager.LoaderCallbacks;
import
android.support.v4.content.Loader;
import
android.support.v4.widget.SimpleCursorAdapter;
import
android.view.View;
import
android.widget.AdapterView;
import
android.widget.AdapterView.OnItemClickListener;
import
android.widget.ListView;
import
android.widget.Toast;
public class MainActivity extends
FragmentActivity implements OnItemClickListener, LoaderCallbacks<Cursor>
{
private static final String WEB_URL = "https://api.github.com/gists/public";
private static final int LOADER_ID = 1234;
private ListView listView;
private
SimpleCursorAdapter adapter;
@Override
protected void onCreate(Bundle
savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView)findViewById(R.id.listView);
String from[] = {"login", "id", "login"};
int to[] = {R.id.loginTextView, R.id.idTextView, R.id.listRowContainer};
adapter = new
SimpleCursorAdapter(this, R.layout.list_row, null, from, to, 0);
adapter.setViewBinder(new
CustomViewBinder(this));
//Comment the above line if You don't
need to customize any view dynamically in ListView rows.
//If you don't comment the above line,
The column names can be in any order in 'from' array.
//You can repeat column names If you
don't have enough columns in your cursor.
//And Specify all the ids of UI
elements in 'to' array which you want to change dynamically
//If you comment the above line, 'to'
array should contain the ids of TextViews or Buttons only.
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
Bundle queryBundle = new Bundle();
queryBundle.putString("url", WEB_URL);
getSupportLoaderManager().initLoader(LOADER_ID, queryBundle, this);
}
@Override
public
Loader<Cursor> onCreateLoader(int loaderId, Bundle queryBundle) {
return new DownLoader(this,
queryBundle.getString("url"));
}
@Override
public void onLoadFinished(final
Loader<Cursor> loader, final Cursor
responseCursor) {
if(loader.getId()
== LOADER_ID && responseCursor != null){
adapter.swapCursor(responseCursor);
//If you want to implement Load more
feature to your listview
//then you can merge the new response
cursor with old cursor and swap it to adapter.
}
}
@Override
public void
onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);
}
@Override
public void
onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
Cursor cursor = ((SimpleCursorAdapter)listView.getAdapter()).getCursor();
Toast.makeText(getApplicationContext(),
cursor.getString(cursor.getColumnIndexOrThrow("login"))+", "+cursor.getString(cursor.getColumnIndexOrThrow("id")), Toast.LENGTH_LONG).show();
}
}
DownLoader.java
:
import
android.content.Context;
import
android.database.Cursor;
import
android.support.v4.content.CursorLoader;
public class DownLoader extends CursorLoader {
private String url;
public
DownLoader(Context context, String url) {
super(context);
this.url = url;
}
@Override
public Cursor
loadInBackground() {
return new
WebServiceStub().getCursorFromWebResponse(url);
}
}
WebServiceStub.java :
import
java.util.ArrayList;
import java.util.List;
import
org.apache.http.HttpEntity;
import
org.apache.http.client.methods.HttpGet;
import
org.apache.http.impl.client.DefaultHttpClient;
import
org.apache.http.util.EntityUtils;
import
org.json.JSONArray;
import
org.json.JSONObject;
import
android.database.Cursor;
import
android.database.MatrixCursor;
public class WebServiceStub
{
//This is the key method which converts the response string as required
cursor object.
public Cursor
getCursorFromWebResponse(String url) {
String response =
getResponseAsString(getWebResponse(url));
MatrixCursor emptyCursor = new MatrixCursor(new String[0]);
MatrixCursor resultCursor;
String keysArray[] = new String[]{"_id", "login", "id"};
////Added extra "_id"
column as adapters need it for traversal
try {
JSONArray mainJsonArray = new
JSONArray(response);
//Create a new Cursor with JSON
keys as columns
resultCursor = new
MatrixCursor(keysArray, mainJsonArray.length());
for (int i = 0; i <
mainJsonArray.length(); i++) {
JSONObject singleJsonObject =
mainJsonArray.getJSONObject(i);
JSONObject userJsonObject =
singleJsonObject.getJSONObject("user");
List<Object> values = new
ArrayList<Object>();
values.add(i);
values.add(userJsonObject.getString(keysArray[1]));
values.add(userJsonObject.getString(keysArray[2]));
resultCursor.addRow(values);
}
return resultCursor;
} catch (Exception e) {
e.printStackTrace();
}
return emptyCursor;
}
private HttpEntity
getWebResponse(String url) {
try {
return new
DefaultHttpClient().execute(new HttpGet(url)).getEntity();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String
getResponseAsString(HttpEntity entity){
try {
return EntityUtils.toString(entity);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
CustomViewBinder.java
import
android.app.Activity;
import
android.database.Cursor;
import android.graphics.Color;
import
android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
import
android.view.View;
import
android.widget.LinearLayout;
import
android.widget.TextView;
public class
CustomViewBinder implements ViewBinder {
int i;
public
CustomViewBinder(Activity act) {
}
@Override
public boolean
setViewValue(View view, Cursor cursor, int pos) {
int id =
view.getId();
if(id == R.id.loginTextView){
((TextView)view).setText(cursor.getString(cursor.getColumnIndexOrThrow("login")));
((TextView)view).setTextColor(Color.BLUE);
}else if(id == R.id.idTextView){
((TextView)view).setText(cursor.getString(cursor.getColumnIndexOrThrow("id")));
((TextView)view).setTextColor(Color.RED);
}else if(id == R.id.listRowContainer){
((LinearLayout)view).setBackgroundColor((pos+i++)%2 == 0 ?
Color.CYAN : Color.WHITE);
}
return true;
}
}
Main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
tools:context=".MainActivity"
>
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cacheColorHint="#00000000" />
</RelativeLayout>
List_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/listRowContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="10dp"
>
<TextView
android:id="@+id/loginTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:singleLine="true"
android:textColor="@android:color/black"
android:textSize="17sp"
android:textStyle="bold"
/>
<TextView
android:id="@+id/idTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:ellipsize="end"
android:maxLines="1"
android:singleLine="true"
android:textColor="@android:color/black"
android:textSize="17sp"
android:textStyle="bold" />
</LinearLayout>
NOTE :
Don’t forget adding INTERNET permission and Min Required sdk = 8 in your
manifest.xml and support library v4 jar file must be available in libs folder.
If your min sdk is above or equal to 11 and not using support library jar then
you should use getLoaderManager() instead of getSupportLoaderManager() and change
required import statements in MainActvity.java.
Why is this THE BEST ?
You don’t need to take care of below things :
You don’t need to take care of below things :
1. Configuration/Orientation changes.
2. Handling Threads, Handlers,
Synchronization with UI thread.
3. UI is responsive all the time.
And ofcource this way gives less
possible errors. Do you need anything extra?
Nice blog..
ReplyDelete