Android Development #3: Populating a Custom ListView From a Remote JSON

First posted on 12/01/2014

Create a new Android Project. I named it ListViewExample. Change the name of the MainActivity to CategoryActivity.
In this note, we will be learning on how to:

  1. Create a ListView
  2. Populate it with data from a remote JSON. You can see the JSON file right here
  3. Creating listener for the listview
  4. Load an image using Picasso Image Loader library

Please download Picasso library and put the .jar file into project's libs folder.

Manifest File

Add Internet Permission into manifest.xml

<uses-permission android:name="android.permission.INTERNET">

XML Layouts

  1. Create a activity_category.xml if you haven't already. This will contains a ListView component in our app. Make sure it has @android:id/list as the ID.

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" >
    
        <ListView
            android:id="@android:id/list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    
    </LinearLayout>
    
  2. Create a custom_list_category.xml to customize our ListView to display Category title, item counts, and an image.

    <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="10dp" >
    
        <TextView
            android:id="@+id/category_id"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:visibility="gone" />
    
        <TextView
            android:id="@+id/category_name"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:hint="Category Name"
            android:paddingBottom="15dip"
            android:paddingLeft="10dip"
            android:paddingTop="15dip"
            android:textColor="#000000"
            android:textSize="16dip"
            android:textStyle="bold" />
    
        <TextView
            android:id="@+id/songs_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:layout_toLeftOf="@+id/txt_item_count"
            android:hint="20"
            android:paddingLeft="3dip"
            android:paddingRight="3dip"
            android:textSize="10sp"
            android:textStyle="bold" />
    
        <TextView
            android:id="@+id/txt_item_count"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_marginTop="20dp"
            android:text="Items"
            android:textSize="10sp" />
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="100dp"
            android:layout_below="@id/category_name"
            android:orientation="vertical" >
    
            <ImageView
                android:id="@+id/img_category_logo"
                android:layout_width="fill_parent"
                android:layout_height="match_parent"
                android:layout_gravity="center"
                android:padding="5dp"
                android:scaleType="centerInside" />
    
        </LinearLayout>
    
    </RelativeLayout>
    

Programming

  1. Create a CategoryActivity class that extends ListActivity and import appropriate packages (CTRL + SHIFT + O).

    public class CategoryActivity extends ListActivity {
    }
    
  2. In onCreate() function, define an ArrayList for the ListView

    public class CategoryActivity extends ListActivity {
     ArrayList<HashMap<String, String>> categoryList;
     ...
    
     @Override
     public void onCreate(Bundle savedInstanceState) {
      ...
    
      categoryList = new ArrayList<HashMap<String, String>>();
      ...
    
      new LoadCategories().execute(); // Execute LoadCategories function
     }
    }
    
  3. We will create a new class to load category listing from a remote JSON file. Name this class as LoadCategories extending AsyncTask.

    Note: AsyncTask is a classic way of making an asynchronous HTTP request while on the main UI thread. If you don't use AsyncTask or any equivalent library (eg. Volley) that perform asynchronous task, your app will catch an error. The meaning of this is to make HTTP request working simultaneously with the UI thread. Well, fuck this. Learn to Android doc.

    class LoadCategories extends AsyncTask&lt;String, String ,String&gt; {
        @Override
        protected void onPreExecute() {
        }
        
        @Override
        protected String doInBackground(String... args) {
            return something;
        }
        
        @Override
        protected void onPostExecute(String json) {
        }
    }
    

    Those are the basic methods used if you extend your class with AsyncTask. The method name is pretty straight forward. onPreExecute method will fire up while doInBackground is executing in the background. onPostExecute will be executed after doInBackground done its job.

  4. Let's fill up the method with our biznez logic.

    We will use onPreExecute to display a loading dialog.

    protected void onPreExecute() {
     super.onPreExecute();
     pDialog = new ProgressDialog(CategoryActivity.this);
     pDialog.setMessage("Listing Categories...");
     pDialog.setIndeterminate(false);
     pDialog.setCancelable(false);
     pDialog.show();
    }
    

    In doInBackground method, we will make a JSON HTTP request from a URL with a parameter.

    protected String doInBackground(String... args){
     // Building Parameters
     List<NameValuePair> params = new ArrayList<NameValuePair>();
    
     // getting JSON string from URL
     String json = jsonParser.makeHttpRequest(URL_CATEGORY, "GET",
       params);
    
     // Check your log cat for JSON reponse
     Log.d("Categories JSON: ", "&gt; " + json);
       
     return json;
    }
    

    After the doInBackground is done, onPostExecute will handle the other job which is populating the ListView with some data from our previously read JSON. Basically, it will fetch the parsed (in other word, downloaded) JSON string and give it a tag, then store it in a map. The way it reads the JSON tree is one other thing I will make a note about it later because as we see in our JSON file, there were parents and child. So, in order to read it, we need to loop according to a proper logic. Let's just not talk about that now.

    @Override
    protected void onPostExecute(String json) {
        try {
            categories = new JSONArray(json);
            
            if (categories != null) {
                // looping through All albums
                for (int i = 0; i &amp;lt; categories.length(); i++) {
                    JSONObject c = categories.getJSONObject(i);
                    
                    // Storing each json item values in variable
                    String id = c.getString(TAG_ID);
                    String name = c.getString(TAG_NAME);
                    String songs_count = c.getString(TAG_CATEGORIES_COUNT);
                    String category_logo = c.getString(TAG_CATEGORIES_LOGO);
                    
                    // creating new HashMap
                    HashMap&lt;String, String&gt; map = new HashMap&lt;String, String&gt;();
                    
                    // adding each child node to HashMap key =&amp;gt; value
                    map.put(TAG_ID, id);
                    map.put(TAG_NAME, name);
                    map.put(TAG_CATEGORIES_COUNT, songs_count);
                    map.put(TAG_CATEGORIES_LOGO, category_logo);
                    
                    // adding HashList to ArrayList
                    categoryList.add(map);
                }
                
                mAdapter = new CategoryListAdapter(CategoryActivity.this, categoryList);
                getListView().setAdapter(mAdapter);
                
                pDialog.dismiss();
                } else {
                Log.d(&quot;Categories: &quot;, &quot;null&quot;);
            }
            
            } catch (JSONException e) {
            e.printStackTrace();
        }
    }
    }
    
  5. Next step is to create an Adapter for our ListView. Create a new class named CategoryListAdapter and extend it to a BaseAdapter. I like to think of this step as an "applying process" for the parsed List item as it reads the map that we have stored all the tags in previously, and we "apply" it into our custom listview layout we have made earlier.

    
    public class CategoryListAdapter extends BaseAdapter {
        
        // class constructor
        public CategoryListAdapter(Context context,
        ArrayList<HashMap<String, String>> items) {
        }
        
        // How many items are in the data set represented by this Adapter.
        public int getCount() {
        }
        
        // Get the data item associated with the specified position in the data set.
        public Object getItem(int position) {
        }
        
        // Get the row id associated with the specified position in the list.
        public long getItemId(int position) {
        }
        
        // displaying the listview with the mapped items
        public View getView(int position, View convertView, ViewGroup parent) {
        }
        
    }
    

    These are all the basic unimplemented methods from BaseAdapter we have extended to our class. We need this to get list item counts, the item id as well as the item itself, individually. getView method is where we apply our map to our custom listview. Let's go through this one by one.

  6. In order to response for a click of an item, we must implement a listener. In onCreate method, set a click listener to the ListView.

    protected void onCreate(Bundle savedInstanceState) {
        ...
        
        // get listview
        ListView lv = getListView();
        lv.setDivider(null);
        
        lv.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView arg0, View view,
            int position, long arg3) {
                
                Toast.makeText(CategoryActivity.this, "Item selected: " + position,
                Toast.LENGTH_LONG).show();
            }
        });
    }
    

    To move from this CategoryActivity to another Activity, let's say the CategoryDetail, simply replace the Toast message to:

    Intent i = new Intent(getApplicationContext(), CategoryDetail.class);
    
    String category_id = ((TextView) view
      .findViewById(R.id.category_id)).getText()
      .toString();
    i.putExtra("category_id", category_id);
    
    startActivity(i);
    

    This will start a new Intent and carry along a category_id to its corresponding CategoryDetails. We use putExtra method to put necessary info for the next Activity. I won't be covering this. Maybe in the next note.

  7. One last thing is the JSON parser utility. We use this in step 4 in doInBackground method. This is a custom class specifically for making JSON HTTP request to the internet. Create a new class named JSONParser.

    package com.aiman.listviewexample;
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.UnsupportedEncodingException;
    import java.util.List;
    
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    import org.apache.http.NameValuePair;
    import org.apache.http.client.ClientProtocolException;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.client.utils.URLEncodedUtils;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import android.util.Log;
    
    public class JSONParser {
        
        static InputStream is = null;
        static JSONObject jObj = null;
        static String json = "";
        
        // constructor
        public JSONParser() {
            
        }
        
        /**
        * Making service call
        *
        * @url - url to make request
        * @method - http request method
        * */
        public String makeHttpRequest(String url, String method) {
            return this.makeHttpRequest(url, method, null);
        }
        
        public JSONObject getJSONFromUrl(String url, List&lt;NameValuePair&gt; params) {
            // Making HTTP request
            try {
                // defaultHttpClient
                DefaultHttpClient httpClient = new DefaultHttpClient();
                HttpPost httpPost = new HttpPost(url);
                httpPost.setEntity(new UrlEncodedFormEntity(params));
                
                HttpResponse httpResponse = httpClient.execute(httpPost);
                HttpEntity httpEntity = httpResponse.getEntity();
                is = httpEntity.getContent();
                
                } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                } catch (ClientProtocolException e) {
                e.printStackTrace();
                } catch (IOException e) {
                e.printStackTrace();
            }
            
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(
                is, "iso-8859-1"), 8);
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }
                is.close();
                json = sb.toString();
                Log.e("JSON", json);
                } catch (Exception e) {
                Log.e("Buffer Error", "Error converting result " + e.toString());
            }
            
            // try parse the string to a JSON object
            try {
                jObj = new JSONObject(json);
                } catch (JSONException e) {
                Log.e("JSON Parser", "Error parsing data " + e.toString());
            }
            
            // return JSON String
            return jObj;
            
        }
        
        // function get json from url
        // by making HTTP POST or GET mehtod
        public String makeHttpRequest(String url, String method,
        List&lt;NameValuePair&gt; params) {
            
            // Making HTTP request
            try {
                DefaultHttpClient httpClient = new DefaultHttpClient();
                // check for request method
                if (method == "POST") {
                    HttpPost httpPost = new HttpPost(url);
                    if (params != null) {
                        httpPost.setEntity(new UrlEncodedFormEntity(params));
                    }
                    HttpResponse httpResponse = httpClient.execute(httpPost);
                    HttpEntity httpEntity = httpResponse.getEntity();
                    is = httpEntity.getContent();
                    } else if (method == "GET") {
                    if (params != null) {
                        String paramString = URLEncodedUtils
                        .format(params, "utf-8");
                        url += "?" + paramString;
                    }
                    HttpGet httpGet = new HttpGet(url);
                    // DefaultHttpClient httpClient = new
                    // DefaultHttpClient(httpParameters);
                    
                    HttpResponse httpResponse = httpClient.execute(httpGet);
                    HttpEntity httpEntity = httpResponse.getEntity();
                    is = httpEntity.getContent();
                }
                
                } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                } catch (ClientProtocolException e) {
                e.printStackTrace();
                } catch (IOException e) {
                e.printStackTrace();
            }
            
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(
                is, "iso-8859-1"), 8);
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }
                is.close();
                json = sb.toString();
                } catch (Exception e) {
                Log.e("Buffer Error", "Error converting result " + e.toString());
            }
            
            // return JSON String
            return json;
            
        }
    }
    

Github Repo

Show Comments

Get the latest posts delivered right to your inbox.