Android Facebook like Custom ListView Feed using Volley

My previous tutorial Android Custom ListView covers customizing android list view with image and text. In this tutorial I am going to explain even more customized list view like Facebook, Google+ news feed where it contains multiple images and texts.

I used android volley network library to make http calls and download the images. As volley comes with powerful image caching mechanism, we don’t have to much worry about caching the requests and images.

android facebook like custom news feed view

Final Output

Following is the screenshot of final output of this tutorial.

android facebook like feed view

Example feed Json

To demonstrate this tutorial I created an example json feed which contains an array of json feed object. Each object defines single feed row where it contains information like feed id, profile pic, profile name, timestamp, status message and feed image.

You can access the json from here.

{
    "feed": [
        {
            "id": 1,
            "name": "National Geographic Channel",
            "image": "http://api.androidhive.info/feed/img/cosmos.jpg",
            "status": "\"Science is a beautiful and emotional human endeavor,\" says Brannon Braga, executive producer and director. \"And Cosmos is all about making science an experience.\"",
            "profilePic": "http://api.androidhive.info/feed/img/nat.jpg",
            "timeStamp": "1403375851930",
            "url": null
        },
        {
            "id": 2,
            "name": "TIME",
            "image": "http://api.androidhive.info/feed/img/time_best.jpg",
            "status": "30 years of Cirque du Soleil's best photos",
            "profilePic": "http://api.androidhive.info/feed/img/time.png",
            "timeStamp": "1403375851930",
            "url": "http://ti.me/1qW8MLB"
        }
    ]
}

In real world scenario, this feed json should be generated dynamically by reading a database.

1. Planning the Layout

Before start writing the code, I would like to plan the layout first. If you observe the feed view, it has information like name,timestamp, profile pic, feed image and url. For this we need TextView, ImageView and a listview to display the feed. Using the LinearLayout’s orientation property I have alligned the elements vertically and horizontally.

android facebook like list view layout

2. Downloading Volley.jar

If you are new to android volley library, I suggest you go through my previous article Android Volley Tutorial to understand what volley library is actually for. (In simple words volley is a networking library used to make HTTP calls)

Download the volley.jar and keep it aside.

Now let’s start by creating a new project.

3. Creating new project

1. Create a new project in Eclipse from File ⇒ New ⇒ Android Application Project and fill all the required information.

2. Open res ⇒ values ⇒ dimens.xml and add following dimensions. If you don’t have dimens.xml, create a new file and add these values.

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>
    
    
    <dimen name="feed_item_margin">10dp</dimen>
    <dimen name="feed_item_padding_top_bottom">20dp</dimen>
    <dimen name="feed_item_padding_left_right">15dp</dimen>
    <dimen name="feed_item_profile_pic">50dp</dimen>
    <dimen name="feed_item_profile_info_padd">10dp</dimen>
    <dimen name="feed_item_profile_name">15dp</dimen>
    <dimen name="feed_item_timestamp">13dp</dimen>
    <dimen name="feed_item_status_pad_left_right">15dp</dimen>
    <dimen name="feed_item_status_pad_top">13dp</dimen>
    <dimen name="feed_item_corner_radius">3dp</dimen>
    <dimen name="feed_item_border_width">1dp</dimen>

</resources>

3. Also create another xml file named colors.xml under res ⇒ values and add following colors.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="white">#ffffff</color>
    <color name="feed_bg">#d3d6db</color>
    <color name="feed_item_bg">#ffffff</color>
    <color name="feed_item_border">#c2c3c8</color>
    <color name="link">#0a80d1</color>
    <color name="timestamp">#a0a3a7</color>    
</resources>

4. Under res ⇒ drawable, create a new file called bg_parent_rounded_corner.xml and paste the below code. This xml will give a rounded corner background to feed item.

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <!-- view background color -->
    <solid android:color="@color/feed_item_bg" >
    </solid>

    <!-- view border color and width -->
    <stroke
        android:width="@dimen/feed_item_border_width"
        android:color="@color/feed_item_border" >
    </stroke>

    <!-- Here is the corner radius -->
    <corners android:radius="@dimen/feed_item_corner_radius" >
    </corners>

</shape>

5. To keep the project well organized, I am creating required packages first. Create 4 packages named app, adapter, data and volley. To create new package, right click on src ⇒ New ⇒ Package and give package name. Example: info.androidhive.listview.app.

Following is the project structure I am trying to achieve.

facebook_feed_eclipse_project

6. Now paste the volley.jar in project libs folder.

7. Create a class named LruBitmapCache.java under volley package and add the following code. This class takes care of caching network images on disk.

package info.androidhive.listviewfeed.volley;

import com.android.volley.toolbox.ImageLoader.ImageCache;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;

public class LruBitmapCache extends LruCache<String, Bitmap> implements
		ImageCache {
	public static int getDefaultLruCacheSize() {
		final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		final int cacheSize = maxMemory / 8;

		return cacheSize;
	}

	public LruBitmapCache() {
		this(getDefaultLruCacheSize());
	}

	public LruBitmapCache(int sizeInKiloBytes) {
		super(sizeInKiloBytes);
	}

	@Override
	protected int sizeOf(String key, Bitmap value) {
		return value.getRowBytes() * value.getHeight() / 1024;
	}

	@Override
	public Bitmap getBitmap(String url) {
		return get(url);
	}

	@Override
	public void putBitmap(String url, Bitmap bitmap) {
		put(url, bitmap);
	}
}

8. Under app package create class named AppController.java and paste the following content. This is a singleton class which initializes global instances of required classes. All the objects related to volley are initialized here.

package info.androidhive.listviewfeed.app;

import info.androidhive.listviewfeed.volley.LruBitmapCache;
import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;

public class AppController extends Application {

	public static final String TAG = AppController.class.getSimpleName();

	private RequestQueue mRequestQueue;
	private ImageLoader mImageLoader;
	LruBitmapCache mLruBitmapCache;

	private static AppController mInstance;

	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
	}

	public static synchronized AppController getInstance() {
		return mInstance;
	}

	public RequestQueue getRequestQueue() {
		if (mRequestQueue == null) {
			mRequestQueue = Volley.newRequestQueue(getApplicationContext());
		}

		return mRequestQueue;
	}

	public ImageLoader getImageLoader() {
		getRequestQueue();
		if (mImageLoader == null) {
			getLruBitmapCache();
			mImageLoader = new ImageLoader(this.mRequestQueue, mLruBitmapCache);
		}

		return this.mImageLoader;
	}

	public LruBitmapCache getLruBitmapCache() {
		if (mLruBitmapCache == null)
			mLruBitmapCache = new LruBitmapCache();
		return this.mLruBitmapCache;
	}

	public <T> void addToRequestQueue(Request<T> req, String tag) {
		req.setTag(TextUtils.isEmpty(tag) ? TAG : tag);
		getRequestQueue().add(req);
	}

	public <T> void addToRequestQueue(Request<T> req) {
		req.setTag(TAG);
		getRequestQueue().add(req);
	}

	public void cancelPendingRequests(Object tag) {
		if (mRequestQueue != null) {
			mRequestQueue.cancelAll(tag);
		}
	}
}

9. Now open your AndroidManifest.xml file and add Application.java class in <application> tag. Also we need to add INTERNET permission as this app makes network calls.

    <application
        android:name="info.androidhive.listviewfeed.app.AppController"> ....</application>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.listviewfeed"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="11"
        android:targetSdkVersion="19" />

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

    <application
        android:name="info.androidhive.listviewfeed.app.AppController"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="info.androidhive.listviewfeed.MainActivity"
            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>

10. The main challenge in this project is adjusting the aspect ratio of feed image once it is downloaded. Unfortunately volley doesn’t provide any callback method once the NetworkImageView is loaded. So I created a custom ImageView class with callback methods. This class automatically adjusts the image height to prevent image aspect ratio distortion.

Under your project main package create a class named FeedImageView.java and paste the below code. I took the code from this stackoverflow answer and made few tweaks.

You can use this FeedImageView in your xml layout like this

<info.androidhive.listviewfeed.FeedImageView
            android:id="@+id/feedImage1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
package info.androidhive.listviewfeed;

import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;

import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.android.volley.toolbox.ImageLoader.ImageListener;

public class FeedImageView extends ImageView {

	public interface ResponseObserver {
		public void onError();

		public void onSuccess();
	}

	private ResponseObserver mObserver;

	public void setResponseObserver(ResponseObserver observer) {
		mObserver = observer;
	}

	/**
	 * The URL of the network image to load
	 */
	private String mUrl;

	/**
	 * Resource ID of the image to be used as a placeholder until the network
	 * image is loaded.
	 */
	private int mDefaultImageId;

	/**
	 * Resource ID of the image to be used if the network response fails.
	 */
	private int mErrorImageId;

	/**
	 * Local copy of the ImageLoader.
	 */
	private ImageLoader mImageLoader;

	/**
	 * Current ImageContainer. (either in-flight or finished)
	 */
	private ImageContainer mImageContainer;

	public FeedImageView(Context context) {
		this(context, null);
	}

	public FeedImageView(Context context, AttributeSet attrs) {
		this(context, attrs, 0);
	}

	public FeedImageView(Context context, AttributeSet attrs,
			int defStyle) {
		super(context, attrs, defStyle);
	}

	/**
	 * Sets URL of the image that should be loaded into this view. Note that
	 * calling this will immediately either set the cached image (if available)
	 * or the default image specified by
	 * {@link VolleyImageView#setDefaultImageResId(int)} on the view.
	 * 
	 * NOTE: If applicable, {@link VolleyImageView#setDefaultImageResId(int)}
	 * and {@link VolleyImageView#setErrorImageResId(int)} should be called
	 * prior to calling this function.
	 * 
	 * @param url
	 *            The URL that should be loaded into this ImageView.
	 * @param imageLoader
	 *            ImageLoader that will be used to make the request.
	 */
	public void setImageUrl(String url, ImageLoader imageLoader) {
		mUrl = url;
		mImageLoader = imageLoader;
		// The URL has potentially changed. See if we need to load it.
		loadImageIfNecessary(false);
	}

	/**
	 * Sets the default image resource ID to be used for this view until the
	 * attempt to load it completes.
	 */
	public void setDefaultImageResId(int defaultImage) {
		mDefaultImageId = defaultImage;
	}

	/**
	 * Sets the error image resource ID to be used for this view in the event
	 * that the image requested fails to load.
	 */
	public void setErrorImageResId(int errorImage) {
		mErrorImageId = errorImage;
	}

	/**
	 * Loads the image for the view if it isn't already loaded.
	 * 
	 * @param isInLayoutPass
	 *            True if this was invoked from a layout pass, false otherwise.
	 */
	private void loadImageIfNecessary(final boolean isInLayoutPass) {
		final int width = getWidth();
		int height = getHeight();

		boolean isFullyWrapContent = getLayoutParams() != null
				&& getLayoutParams().height == LayoutParams.WRAP_CONTENT
				&& getLayoutParams().width == LayoutParams.WRAP_CONTENT;
		// if the view's bounds aren't known yet, and this is not a
		// wrap-content/wrap-content
		// view, hold off on loading the image.
		if (width == 0 && height == 0 && !isFullyWrapContent) {
			return;
		}

		// if the URL to be loaded in this view is empty, cancel any old
		// requests and clear the
		// currently loaded image.
		if (TextUtils.isEmpty(mUrl)) {
			if (mImageContainer != null) {
				mImageContainer.cancelRequest();
				mImageContainer = null;
			}
			setDefaultImageOrNull();
			return;
		}

		// if there was an old request in this view, check if it needs to be
		// canceled.
		if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
			if (mImageContainer.getRequestUrl().equals(mUrl)) {
				// if the request is from the same URL, return.
				return;
			} else {
				// if there is a pre-existing request, cancel it if it's
				// fetching a different URL.
				mImageContainer.cancelRequest();
				setDefaultImageOrNull();
			}
		}

		// The pre-existing content of this view didn't match the current URL.
		// Load the new image
		// from the network.
		ImageContainer newContainer = mImageLoader.get(mUrl,
				new ImageListener() {
					@Override
					public void onErrorResponse(VolleyError error) {
						if (mErrorImageId != 0) {
							setImageResource(mErrorImageId);
						}

						if (mObserver != null) {
							mObserver.onError();
						}
					}

					@Override
					public void onResponse(final ImageContainer response,
							boolean isImmediate) {
						// If this was an immediate response that was delivered
						// inside of a layout
						// pass do not set the image immediately as it will
						// trigger a requestLayout
						// inside of a layout. Instead, defer setting the image
						// by posting back to
						// the main thread.
						if (isImmediate && isInLayoutPass) {
							post(new Runnable() {
								@Override
								public void run() {
									onResponse(response, false);
								}
							});
							return;
						}

						int bWidth = 0, bHeight = 0;
						if (response.getBitmap() != null) {

							setImageBitmap(response.getBitmap());
							bWidth = response.getBitmap().getWidth();
							bHeight = response.getBitmap().getHeight();
							adjustImageAspect(bWidth, bHeight);

						} else if (mDefaultImageId != 0) {
							setImageResource(mDefaultImageId);
						}

						if (mObserver != null) {
							mObserver.onSuccess();

						}
					}
				});

		// update the ImageContainer to be the new bitmap container.
		mImageContainer = newContainer;

	}

	private void setDefaultImageOrNull() {
		if (mDefaultImageId != 0) {
			setImageResource(mDefaultImageId);
		} else {
			setImageBitmap(null);
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		loadImageIfNecessary(true);
	}

	@Override
	protected void onDetachedFromWindow() {
		if (mImageContainer != null) {
			// If the view was bound to an image request, cancel it and clear
			// out the image from the view.
			mImageContainer.cancelRequest();
			setImageBitmap(null);
			// also clear out the container so we can reload the image if
			// necessary.
			mImageContainer = null;
		}
		super.onDetachedFromWindow();
	}

	@Override
	protected void drawableStateChanged() {
		super.drawableStateChanged();
		invalidate();
	}

	/*
	 * Adjusting imageview height
	 * */
	private void adjustImageAspect(int bWidth, int bHeight) {
		LinearLayout.LayoutParams params = (LayoutParams) getLayoutParams();

		if (bWidth == 0 || bHeight == 0)
			return;

		int swidth = getWidth();
		int new_height = 0;
		new_height = swidth * bHeight / bWidth;
		params.width = swidth;
		params.height = new_height;
		setLayoutParams(params);
	}
}

11. Open your layout for main activity (activity_main.xml) and add a list view element for the feed list.

<?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"
        android:divider="@null" />

</LinearLayout>

12. Create another layout file named feed_item.xml under res ⇒ layout folder. This layout file represents each individual feed item row in the list view.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/feed_bg"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"

        android:layout_marginLeft="@dimen/feed_item_margin"
        android:layout_marginRight="@dimen/feed_item_margin"
        android:layout_marginTop="@dimen/feed_item_margin"
        android:background="@drawable/bg_parent_rounded_corner"
        android:orientation="vertical"
        android:paddingBottom="@dimen/feed_item_padding_top_bottom"
        android:paddingTop="@dimen/feed_item_padding_top_bottom" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:paddingLeft="@dimen/feed_item_padding_left_right"
            android:paddingRight="@dimen/feed_item_padding_left_right" >

            <com.android.volley.toolbox.NetworkImageView
                android:id="@+id/profilePic"
                android:layout_width="@dimen/feed_item_profile_pic"
                android:layout_height="@dimen/feed_item_profile_pic"
                android:scaleType="fitCenter" >
            </com.android.volley.toolbox.NetworkImageView>

            <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:paddingLeft="@dimen/feed_item_profile_info_padd" >

                <TextView
                    android:id="@+id/name"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:textSize="@dimen/feed_item_profile_name"
                    android:textStyle="bold" />

                <TextView
                    android:id="@+id/timestamp"
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:textColor="@color/timestamp"
                    android:textSize="@dimen/feed_item_timestamp" />
            </LinearLayout>
        </LinearLayout>

        <TextView
            android:id="@+id/txtStatusMsg"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="5dp"
            android:paddingLeft="@dimen/feed_item_status_pad_left_right"
            android:paddingRight="@dimen/feed_item_status_pad_left_right"
            android:paddingTop="@dimen/feed_item_status_pad_top" />

        <TextView
            android:id="@+id/txtUrl"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:linksClickable="true"
            android:paddingBottom="10dp"
            android:paddingLeft="@dimen/feed_item_status_pad_left_right"
            android:paddingRight="@dimen/feed_item_status_pad_left_right"
            android:textColorLink="@color/link" />

        <info.androidhive.listviewfeed.FeedImageView
            android:id="@+id/feedImage1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white"
            android:scaleType="fitXY"
            android:visibility="visible" />
    </LinearLayout>

</LinearLayout>

13. Under data package, create a class named FeedItem.java. This is a POJO class used to create objects for each feed item while parsing the json. The feed item object contains information like profile pic, name, timestamp, status message, url and feed image.

package info.androidhive.listviewfeed.data;

public class FeedItem {
	private int id;
	private String name, status, image, profilePic, timeStamp, url;

	public FeedItem() {
	}

	public FeedItem(int id, String name, String image, String status,
			String profilePic, String timeStamp, String url) {
		super();
		this.id = id;
		this.name = name;
		this.image = image;
		this.status = status;
		this.profilePic = profilePic;
		this.timeStamp = timeStamp;
		this.url = url;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getImge() {
		return image;
	}

	public void setImge(String image) {
		this.image = image;
	}

	public String getStatus() {
		return status;
	}

	public void setStatus(String status) {
		this.status = status;
	}

	public String getProfilePic() {
		return profilePic;
	}

	public void setProfilePic(String profilePic) {
		this.profilePic = profilePic;
	}

	public String getTimeStamp() {
		return timeStamp;
	}

	public void setTimeStamp(String timeStamp) {
		this.timeStamp = timeStamp;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}
}

14. Now under adapter package create a class named FeedListAdapter.java. This is a custom adapter class for feed list view. This adapter class takes care following things.

> Displaying feed data like name, timestamp, profile pic, status message and feed image.
> Converts timestamp into x minutes/hours/days ago format
> Makes URL clickable by using url.setMovementMethod(LinkMovementMethod.getInstance())

package info.androidhive.listviewfeed.adapter;

import info.androidhive.listviewfeed.FeedImageView;
import info.androidhive.listviewfeed.R;
import info.androidhive.listviewfeed.app.AppController;
import info.androidhive.listviewfeed.data.FeedItem;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.text.Html;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;

public class FeedListAdapter extends BaseAdapter {	
	private Activity activity;
	private LayoutInflater inflater;
	private List<FeedItem> feedItems;
	ImageLoader imageLoader = AppController.getInstance().getImageLoader();

	public FeedListAdapter(Activity activity, List<FeedItem> feedItems) {
		this.activity = activity;
		this.feedItems = feedItems;
	}

	@Override
	public int getCount() {
		return feedItems.size();
	}

	@Override
	public Object getItem(int location) {
		return feedItems.get(location);
	}

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

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		if (inflater == null)
			inflater = (LayoutInflater) activity
					.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
		if (convertView == null)
			convertView = inflater.inflate(R.layout.feed_item, null);

		if (imageLoader == null)
			imageLoader = AppController.getInstance().getImageLoader();

		TextView name = (TextView) convertView.findViewById(R.id.name);
		TextView timestamp = (TextView) convertView
				.findViewById(R.id.timestamp);
		TextView statusMsg = (TextView) convertView
				.findViewById(R.id.txtStatusMsg);
		TextView url = (TextView) convertView.findViewById(R.id.txtUrl);
		NetworkImageView profilePic = (NetworkImageView) convertView
				.findViewById(R.id.profilePic);
		FeedImageView feedImageView = (FeedImageView) convertView
				.findViewById(R.id.feedImage1);

		FeedItem item = feedItems.get(position);

		name.setText(item.getName());

		// Converting timestamp into x ago format
		CharSequence timeAgo = DateUtils.getRelativeTimeSpanString(
				Long.parseLong(item.getTimeStamp()),
				System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS);
		timestamp.setText(timeAgo);

		// Chcek for empty status message
		if (!TextUtils.isEmpty(item.getStatus())) {
			statusMsg.setText(item.getStatus());
			statusMsg.setVisibility(View.VISIBLE);
		} else {
			// status is empty, remove from view
			statusMsg.setVisibility(View.GONE);
		}

		// Checking for null feed url
		if (item.getUrl() != null) {
			url.setText(Html.fromHtml("<a href=\"" + item.getUrl() + "\">"
					+ item.getUrl() + "</a> "));

			// Making url clickable
			url.setMovementMethod(LinkMovementMethod.getInstance());
			url.setVisibility(View.VISIBLE);
		} else {
			// url is null, remove from the view
			url.setVisibility(View.GONE);
		}

		// user profile pic
		profilePic.setImageUrl(item.getProfilePic(), imageLoader);

		// Feed image
		if (item.getImge() != null) {
			feedImageView.setImageUrl(item.getImge(), imageLoader);
			feedImageView.setVisibility(View.VISIBLE);
			feedImageView
					.setResponseObserver(new FeedImageView.ResponseObserver() {
						@Override
						public void onError() {
						}

						@Override
						public void onSuccess() {
						}
					});
		} else {
			feedImageView.setVisibility(View.GONE);
		}

		return convertView;
	}

}

15. Now we have all the required classes in place. Open your main activity class MainActivity.java and add the following code. Here using volley JsonObjectRequest I fetched the json, parsed it(created array of feed item objects) and passed the data to list view adapter.

package info.androidhive.listviewfeed;

import info.androidhive.listviewfeed.adapter.FeedListAdapter;
import info.androidhive.listviewfeed.app.AppController;
import info.androidhive.listviewfeed.data.FeedItem;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.view.Menu;
import android.widget.ListView;

import com.android.volley.Cache;
import com.android.volley.Cache.Entry;
import com.android.volley.Request.Method;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.VolleyLog;
import com.android.volley.toolbox.JsonObjectRequest;

public class MainActivity extends Activity {
	private static final String TAG = MainActivity.class.getSimpleName();
	private ListView listView;
	private FeedListAdapter listAdapter;
	private List<FeedItem> feedItems;
	private String URL_FEED = "http://api.androidhive.info/feed/feed.json";

	@SuppressLint("NewApi")
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

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

		feedItems = new ArrayList<FeedItem>();

		listAdapter = new FeedListAdapter(this, feedItems);
		listView.setAdapter(listAdapter);
		
		// These two lines not needed,
		// just to get the look of facebook (changing background color & hiding the icon)
		getActionBar().setBackgroundDrawable(new ColorDrawable(Color.parseColor("#3b5998")));
		getActionBar().setIcon(
				   new ColorDrawable(getResources().getColor(android.R.color.transparent)));

		// We first check for cached request
		Cache cache = AppController.getInstance().getRequestQueue().getCache();
		Entry entry = cache.get(URL_FEED);
		if (entry != null) {
			// fetch the data from cache
			try {
				String data = new String(entry.data, "UTF-8");
				try {
					parseJsonFeed(new JSONObject(data));
				} catch (JSONException e) {
					e.printStackTrace();
				}
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}

		} else {
			// making fresh volley request and getting json
			JsonObjectRequest jsonReq = new JsonObjectRequest(Method.GET,
					URL_FEED, null, new Response.Listener<JSONObject>() {

						@Override
						public void onResponse(JSONObject response) {
							VolleyLog.d(TAG, "Response: " + response.toString());
							if (response != null) {
								parseJsonFeed(response);
							}
						}
					}, new Response.ErrorListener() {

						@Override
						public void onErrorResponse(VolleyError error) {
							VolleyLog.d(TAG, "Error: " + error.getMessage());
						}
					});

			// Adding request to volley request queue
			AppController.getInstance().addToRequestQueue(jsonReq);
		}

	}

	/**
	 * Parsing json reponse and passing the data to feed view list adapter
	 * */
	private void parseJsonFeed(JSONObject response) {
		try {
			JSONArray feedArray = response.getJSONArray("feed");

			for (int i = 0; i < feedArray.length(); i++) {
				JSONObject feedObj = (JSONObject) feedArray.get(i);

				FeedItem item = new FeedItem();
				item.setId(feedObj.getInt("id"));
				item.setName(feedObj.getString("name"));

				// Image might be null sometimes
				String image = feedObj.isNull("image") ? null : feedObj
						.getString("image");
				item.setImge(image);
				item.setStatus(feedObj.getString("status"));
				item.setProfilePic(feedObj.getString("profilePic"));
				item.setTimeStamp(feedObj.getString("timeStamp"));

				// url might be null sometimes
				String feedUrl = feedObj.isNull("url") ? null : feedObj
						.getString("url");
				item.setUrl(feedUrl);

				feedItems.add(item);
			}

			// notify data changes to list adapater
			listAdapter.notifyDataSetChanged();
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}

Now run the project and test it once. Make sure that your emulator/device is having internet connect before you test.

Ravi is hardcore Android programmer and Android programming has been his passion since he compiled his first hello-world program. Solving real problems of Android developers through tutorials has always been interesting part for him.