How to Lazy load images with JSON parsing and infinite scrolling on Android

Heyaa All! Its been a long time. I am learning more and more things about Android development. There are many awesome libraries out there that not only helps in improving your application performance but also has excellent design. I have seen many online gallery apps that shows images with infinite scrolling. You just need to scroll down to see more. It all happens automatically. So How it is happening? I figured out a way to do so using some JSON and PHP and then parsing json data onto android application.

First of all, Lets see What we need:

Prerequisites:

Lets say, I have database that have following structure( This database is not well-designed. I made it a year ago for a wallpaper app. So just use it as a reference.):

Screenshot_1

You can download .jar file of Universal Image Loader from github.

Now, Lets see What we are actually going to do:

Implement Paging

Showing limited JSON data per page through paging so that we do not need to download large amount of JSON data at once. We can divide it into different pages.

For example, Lets say there is 2000 images of data in the database. If you are going to fetch data from the database, the query will take a lot of time to show the data for 2000 images. What you can do is to divide that data into different pages i.e. show only 20 images of data per page. You need to just change page parameter for every page like this:

Screenshot_2

Here’s code for implementing paging through PHP :


<?php
header('Content-Type: application/json');
$dbhost = 'localhost';
$dbuser = 'YOUR_DB_USERNAME';
$dbpass = 'YOUR_DB_PASS';
$dbname = 'YOUR_DB_NAME';

//setting records limit per page is 21
$rec_limit = 21;

//Establishing Connection
$conn = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
if (mysqli_connect_errno()) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

/* Get total number of records */
$sql = "SELECT count(*) FROM posts ";
$retval = $conn->query($sql);
if(! $retval )
{
  die($mysqli->error.__LINE__);
}

$rec_count = $retval->fetch_row();
$rec_count = $rec_count[0];

// Checking for page parameter to set.
if( isset($_GET{'page'} ) )
{
   $page = $_GET{'page'} + 1;
   $offset = $rec_limit * $page ;
}
else
{
   $page = 0;
   $offset = 0;
}

//getting all data from table
$sql = "SELECT * FROM posts ORDER BY id DESC ".
       "LIMIT $offset, $rec_limit";
    
$retval = $conn->query($sql);
if(! $retval )
{
 die($mysqli->error.__LINE__);
}

//creating an array for response
 $response = array();
 
 if ($retval->num_rows > 0) {
 $response["wallpapers"] = array();
 $response["success"] = 1;
 $response["count"]= $rec_count;
 while ($row = $retval->fetch_array()) {
        // temp wallpaper array
        $wallpaper = array();
        $wallpaper["id"] = $row["id"];
        $wallpaper["orig_url"] = "http://walldroidhd.com/orig/".$row["id"]."_" .$row["title"].".".$row["extension"];
        $wallpaper["thumb_url"] = "http://walldroidhd.com/thumb/thumb_160x160_".$row["id"]."_" .$row["title"].".".$row["extension"];
        $wallpaper["downloads"] = $row["downloads"];
        $wallpaper["fav"] = $row["fav"];
 
        
        // push all data into final response array
        array_push($response["wallpapers"], $wallpaper);
    }

    // echoing JSON response
   echo str_replace('\/','/',json_encode($response,JSON_PRETTY_PRINT));

} else {
    // no wallpapers found
    $response["success"] = 0;
    $response["message"] = "No Wallpapers found";
}

mysqli_close($conn);
?>

 

You will see the response like this : http://walldroidhd.com/api.php

Screenshot_3

Now, We have got the data that is going to be used in our android app. We will parse this JSON data and show wallpapers per page.

Creating Android Application

1.  Create a new project in Eclipse from File => New => Android Application Project and fill all the required details. My project name is lazyloading and package name is com.labs.binarywall.lazyloading

2. Download Universal Image Loader library and paste it in libs folder

3. In this project, we are going to use 2 activities and a total of 7 classes:

  • ConnectionDetector.java: This class is used for check if internet is present or not.
  • CustomHttpClient.java: This class is used for getting response from the web server where the database is located or where we have implemented paging.
  • ImageExtractor.java: This class is used for getting data from the server
  • TouchImageView.java: This  is a extended Imageview which has pinch and zoom functionality.
  • UILApplication.java: This class extends Application class and used for setting the UIL image loader configuration.
  • MainActivity.java (Activity) : This is the main activity where images are shown in a gridview
  • ImageDetails.java (Activity): This is the second activity which opens when we clicked on a gridview item.

4. Okay, Now first we setup image loader configuration in UILApplication class.

UILApplication.java

package com.labs.binarywall.lazyloading;

import java.io.File;

import android.app.Application;

import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.utils.StorageUtils;

public class UILApplication extends Application {

	@Override
	public void onCreate() {
		super.onCreate();
		File cacheDir = StorageUtils.getCacheDirectory(getApplicationContext());
		// This configuration tuning is custom. You can tune every option, you may tune some of them,
		// or you can create default configuration by
		//  ImageLoaderConfiguration.createDefault(this);
		// method.
		ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext())
			.threadPoolSize(1)
			.threadPriority(Thread.NORM_PRIORITY - 2)
			.memoryCacheSize(1500000) // 1.5 Mb
			.denyCacheImageMultipleSizesInMemory()
			.diskCacheFileNameGenerator(new Md5FileNameGenerator())
			.diskCache(new UnlimitedDiscCache(cacheDir)) // default
        .diskCacheSize(50 * 1024 * 1024)
        .diskCacheFileCount(100)
			.build();
		// Initialize ImageLoader with configuration.
		ImageLoader.getInstance().init(config);
	}
}

You can customize your configuration by going through the documentation.

4. UILApplication.java should be executed When app is launched. It is used to get instance for imageloader (It is a singleton class). So, declare this class in the AndroidManifest.xml file as well. Also add the required permissions for sending requests to the server.

Permissions used:

  • android.permission.INTERNET: for sending requests to the server.
  • android.permission.WRITE_EXTERNAL_STORAGE: used for storing cached images.

AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.labs.binarywall.lazyloading"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />

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

    <application
        android:name="com.labs.binarywall.lazyloading.UILApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".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>

            <activity
            android:name=".ImageDetails"
            android:label="@string/app_name" />

    </application>

</manifest>

5. Now, lets create another class ImageExtractor.java that will used to get JSON response from the server and then parse that JSON data to store in a list. Note that If the json starts with {, it is considered to be JSON Object. As well if the json starts with [, then it is JSON Array.

Since our JSON data contains three fields: wallpapers (which is an JSON array that have data for wallpapers), success(JSONObject for checking success) and count(JSONObject showing total number of wallpapers), we need two functions:

int getCount() : this function will helps us in implementing paging on Android. The total count helps us in knowing when will the list end after going through successive number of pages. So we are getting response from webserver using CustomHttpClient.java class using GET method.


	public int getCount()
	{
		int count = 0;

		 try{
			 String response = CustomHttpClient.executeHttpGet(getUrl());

			 String result = response.toString(); 

				JSONObject json = new JSONObject(result);

				try {

	                int success = json.getInt(TAG_SUCCESS);
	                if(success ==1){
	                count = json.getInt("count");
	                }

				}
	        		catch(JSONException e){
	        		        Log.e("log_tag", "Error parsing data "+e.toString());
	        		}
	            }
	            catch (Exception e) {
	        Log.e("log_tag","Error in http connection!!" + e.toString());
	        }

		return count;
	}

Here,we parse the response into JSONObject and getting values for success and count.

ArrayList<HashMap<String, String>> getImages() : It is another data that parse the JSON data in the wallpaper array. Then, Store the data one by one into a HashMap as Key-value pairs. Then, add all wallpapers into arraylist. Therefore, this function is returning arraylist of hashmaps.


{
 "wallpapers": [
 {
 "id": "1753",
 "orig_url": "http://walldroidhd.com/orig/1753_badass_popeye_Fantasy_1600x1200.jpg",
 "thumb_url": "http://walldroidhd.com/thumb/thumb_160x160_1753_badass_popeye_Fantasy_1600x1200.jpg",
 "downloads": "15",
 "fav": "13"
 }
 ],
 "success": 1,
 "count": "1697"
 }

	public ArrayList<HashMap<String, String>> getImages() throws IOException {

		String previewJPGURL = null;
		String viewJPGURL = null;

		 String response = null;
		 ArrayList<HashMap<String, String>> data = new ArrayList<HashMap<String, String>>();

		 try{
		 response = CustomHttpClient.executeHttpGet(getUrl());

		 String result = response.toString(); 

		 JSONObject json = new JSONObject(result);

			try {

				// Checking for sucess
                int success = json.getInt(TAG_SUCCESS);

                if (success == 1) {
				// Getting Array of Wallpapers
				wallpapers = json.getJSONArray("wallpapers");

				// looping through All wallpapers
				for(int i = 0; i < wallpapers.length(); i++){
					JSONObject json_data = wallpapers.getJSONObject(i);
		                String id =  String.valueOf(json_data.getInt("id"));
		             	String download = String.valueOf(json_data.getInt("downloads"));
		             	String fav = String.valueOf(json_data.getInt("fav"));

		             	viewJPGURL = json_data.getString("orig_url");

		                previewJPGURL = json_data.getString("thumb_url");

		                // creating new HashMap
		                HashMap<String, String> jpgs = new HashMap<String, String>();

		                // adding each child node to HashMap key =>
		                // value
		                jpgs.put(PREVIEW_IMAGES, previewJPGURL);
		                jpgs.put(ORIGINAL_IMAGES, viewJPGURL);
		                jpgs.put("id", id);
		                jpgs.put("downloads", download);
		                jpgs.put("fav", fav);

		                // adding HashList to ArrayList

		                data.add(jpgs);
		            	Log.i("hash",
				                 "images," + data); 

		        }
                } else {
                 data=null;

                }

		}
				catch(JSONException e){
				        Log.e("log_tag", "Error parsing data "+e.toString());
				}
			    }
			    catch (Exception e) {
			Log.e("log_tag","Error in http connection!!" + e.toString());
			}
		return data;
	}

6. Now, lets to res=>layout folder and creating a layout file for MainActivity.java (gridview activity) named activity_main.xml. In this file, we are going to add GridView layout for showing images in grids and other things such as Textviews for showing no connection message and no results found and spinners for loading new data.

activity_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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.labs.binarywall.lazyloading.MainActivity" >

	<GridView
	    xmlns:android="http://schemas.android.com/apk/res/android"
	    android:id="@+id/gridview"
	    android:layout_width="fill_parent"
	    android:layout_height="fill_parent"
	    android:layout_above="@+id/new_photos_loading"
	    android:columnWidth="100dip"
	    android:gravity="center"
	    android:horizontalSpacing="1dip"
	    android:numColumns="2"
	    android:stretchMode="columnWidth"
	    android:verticalSpacing="4dip"
	    android:visibility="visible" />

	<ProgressBar
        android:id="@+id/preview_img_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:visibility="gone" />

	<TextView
        android:id="@+id/no_connect_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Error"
        android:visibility="gone">
    </TextView>

	<ProgressBar
	    android:id="@+id/new_photos_loading"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:layout_alignParentBottom="true"
	    android:layout_centerHorizontal="true"
	    android:visibility="gone" />

	<TextView
	    android:id="@+id/tresults"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    android:layout_centerInParent="true"
	    android:textSize="20sp" />

</RelativeLayout>

Also, we need to add layout for each item of the gridview. So, we create another xml file i.e. item_grid_image.xml and adds the imageview to it.


<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/image"
    android:layout_width="fill_parent"
    android:layout_height="150dip"
    android:adjustViewBounds="true"
    android:scaleType="centerCrop" />

7. Now, lets go to MainActivity.java, We are doing various functions. Before going into those functions, lets take a look at the OnCreate() function of this activity:


@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		noConnectTextView = (TextView) findViewById(R.id.no_connect_message);

		cd = new ConnectionDetector(this);

		 results = (TextView) findViewById(R.id.tresults);

		 isInternetPresent = cd.isConnectingToInternet();

			// check for Internet status
			if (isInternetPresent) {
				imageUrls = new ArrayList<String>();
				previewImageUrls = new ArrayList<String>();
				downloads = new ArrayList<String>();
				fav = new ArrayList<String>();
				ids = new ArrayList<String>();

				//initializing gridview
				initGridView();

				//executing async task for getting data
			downloadPreviewImagesTask = getDownloadPreviewImagesTask(R.id.preview_img_loading, geturl());
			if (downloadPreviewImagesTask != null && downloadPreviewImagesTask.getStatus() == AsyncTask.Status.RUNNING) {
				downloadPreviewImagesTask.attachActivity(this);
				downloadPreviewImagesTask.setSpinnerVisible();

    		} else {
    			Log.d("downloadPreviewImagesTask", "downloadPreviewImagesTask has executed already");
    		}
		}
		else
		{

			showAlertDialog(MainActivity.this, "No Internet Connection",
					"You don't have internet connection!", false);
		}

	}

Here, we are doing various tasks such as initializing layouts, running async tasks, checking for internet connection etc. Lets start with :

initGridView(): This function is used for initializing the gridview and DisplayImageOptions for Universal Image Loader.


	private void initGridView() {
		gridView = (GridView) findViewById(R.id.gridview);
		imageAdapter = new ImageAdapter();
		gridView.setAdapter(imageAdapter);
		gridView.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
				startImageGalleryActivity(position);
			}
		});
		gridView.setOnScrollListener(new EndlessListListener());
	      options = new DisplayImageOptions.Builder()
			.showImageOnLoading(R.drawable.stub_image)
			.showImageForEmptyUri(R.drawable.image_for_empty_url)
			.showImageOnFail(R.drawable.image_for_empty_url)
			.cacheInMemory(true)
			.cacheOnDisk(true)
			.considerExifParams(true)
			.bitmapConfig(Bitmap.Config.RGB_565)
			.build();
	}

DownloadPreviewImagesTask (Async Task) : used for getting latest data from the server using ImageExtractor class. Here, getDownloadPreviewImagesTask(int spinnerID, String url) is used for executing this async task. In the background method, It is calling methods of ImageExtractor class which returns Arraylist of Hashmap of all images data which is then stored into list using initOriginalAndPreviewUrls() method at the end. This async task is called every time user reaches the end of it scroll.


	private DownloadPreviewImagesTask getDownloadPreviewImagesTask(int spinnerID, String url) {
		downloadPreviewImagesTask = new DownloadPreviewImagesTask();
		downloadPreviewImagesTask.setSpinner(spinnerID);
		downloadPreviewImagesTask.attachActivity(this);
		downloadPreviewImagesTask.execute(url); // show spinner
		return downloadPreviewImagesTask;
	}

	private void initOriginalAndPreviewUrls(ArrayList<HashMap<String, String>> images) {

		for(int i=0;i<=images.size()-1;i++){

			String vurls=  images.get(i).get(ImageExtractor.ORIGINAL_IMAGES).toString();
			String purls=  images.get(i).get(ImageExtractor.PREVIEW_IMAGES).toString();
			String download=  images.get(i).get("downloads").toString();
			String ifav=  images.get(i).get("fav").toString();
			String ide=  images.get(i).get("id").toString();
			imageUrls.add(vurls);
			previewImageUrls.add(purls);
			downloads.add(download);
			fav.add(ifav);
			ids.add(ide);
		}

	}

        private void showError(Exception exc) {
		noConnectTextView.setText(exc.getMessage());
		noConnectTextView.setVisibility(View.VISIBLE);
	}

//Async Task for downloading new data
	class DownloadPreviewImagesTask extends AsyncTask<String, Void, ArrayList<HashMap<String, String>>> {

		private Exception exc = null;
		private ProgressBar spinner;
		private MainActivity imageGridActivity = null;
		private int spinnerId;

		public AsyncTask<String, Void, ArrayList<HashMap<String, String>>> setSpinner(int spinnerId) {
			this.spinnerId = spinnerId;
			return this;
		}

		public void attachActivity(MainActivity imageGridActivity) {
			this.imageGridActivity = imageGridActivity;
		}

		public void detachActivity() {
			this.imageGridActivity = null;
		}

		public ProgressBar getSpinner() {
			return spinner;
		}

		@Override
        protected ArrayList<HashMap<String, String>> doInBackground(String... params) {

            String url = "";
            if( params.length > 0 ){
            	url = params[0];
            }

            ImageExtractor imageExtractor = new ImageExtractor(url);
            ArrayList<HashMap<String, String>> images = null;
    		try {
    			images = imageExtractor.getImages();
    			 count = imageExtractor.getCount();

    		} catch (IOException e) {
    			exc = e;
    			Log.e("catched_error", e.getMessage(), e);
    		}
			return images;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            setSpinnerVisible();
        }

        public void setSpinnerVisible() {
        	spinner = (ProgressBar) imageGridActivity.findViewById(spinnerId);
            spinner.setVisibility(View.VISIBLE);
        }

        @Override
        protected void onPostExecute(ArrayList<HashMap<String, String>> images) {
            super.onPostExecute(images);
            spinner.setVisibility(View.GONE);
            if (exc != null) {
            	imageGridActivity.showError(exc);
    		} else if (images !=null) {
    			imageGridActivity.initOriginalAndPreviewUrls(images);

    			if (gridView == null && imageAdapter == null) {
    				initGridView();
    			} else
    				imageGridActivity.getImageAdapter().notifyDataSetChanged();
    		}
    		else
    		{
    		results.setText("Sorry! No Results Found!");
    		}
        }
    }

EndlessListListener (Which is attached to gridview in initGridView() method): This listener is used to implement infinite scrolling in this activity. Whenever the user is about to reach the end of the activity, It calls the DownloadPreviewImagesTask (Async Task) to load new data and attach to the gridview. Actually, It is scroll listener that listens to your scroll on Gridview. There are two functions in the OnScrollListener : onScroll and onScrollStateChanged.

onScroll() Method: In this method, I have implemented this logic When the old data has reached its half, It will start requesting for the new data. Here in this example, I am getting 21 wallpapers as a JSON response from the server. When the scroll reaches 11 or 13 items, It will start loading new data. This method has various parameters that tells about total number of items currently in gridview, no. of currently visible items and index of first visible item.. So you can use these parameter to implement your own logic for sending new request to the server.


	class EndlessListListener implements OnScrollListener {

		private int visibleThreshold = 5;  //setting visible threshold for loading new data
        private int previousTotal = 0;
        private boolean loading = true;

		public void onScroll(AbsListView view, int firstVisible, int visibleCount, int totalCount) {

			if (loading) {
                if (totalCount > previousTotal) {
                    loading = false;
                    previousTotal = totalCount;
                }
            }

			//Log.i("check", String.valueOf(totalCount) + " " + String.valueOf(visibleCount) + " " + String.valueOf(firstVisible));
            if (!loading && (totalCount - visibleCount) <= (firstVisible + visibleThreshold)) {

            	Log.d("loadData", "new data loaded");
	    		String photosUri = ImageExtractor.PHOTOS_URI;

	    		String url = geturl() + photosUri.replace(ImageExtractor.FROM_REPLACEMENT, String.valueOf(page));;

	    		if (downloadPreviewImagesTask != null && downloadPreviewImagesTask.getStatus() == AsyncTask.Status.RUNNING) {
	    			Log.d("downloadPreviewImagesTask", "downloadPreviewImagesTask is running");
	    		}
	    		else  if(page<(count/21)) //checking if there is more data
	    		{
	    			downloadPreviewImagesTask = getDownloadPreviewImagesTask(R.id.new_photos_loading, url);
	    			Log.i("url", url);
	    			page = page+1;
	    		}
                loading = true;
            }

		}

		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {

		}

	}

startImageGalleryActivity() method: This method is used for navigating to another activity with the data. In this method, intent is attached with serialized data about all wallpapers that are loaded in the MainActivity (Grid Activity). All the lists which are initialized are sent over intent to another activity which can also use this data for showing information about the image.


	private void startImageGalleryActivity(int position) {
		Intent intent = new Intent(this, ImageDetails.class);
		intent.putExtra(ImageExtractor.IMAGES, (Serializable)imageUrls);
		intent.putExtra(ImageExtractor.PREVIEW_IMAGES, (Serializable)previewImageUrls);
		intent.putExtra(ImageExtractor.DOWNLOADS,(Serializable) downloads);
	    intent.putExtra(ImageExtractor.FAV, (Serializable) fav);
		intent.putExtra(ImageExtractor.IMAGE_POSITION, position);
		intent.putExtra(ImageExtractor.ID, (Serializable) ids);

		startActivity(intent);
	}

ImageAdapter (Inner class):  Used for setting the data for each gridview item. Here we are actually using the Universal Image Loader for displaying image in the gridview. In the getView() method, after initializing the gridview item, we can set the image to the gridview item layout using imageLoader.displayImage() method. As mentioned in the documentation, it takes more than 2 parameters :

  • image URI : It can be file, URL, assets URI or drawable, you can pass any of these.
  • imageView: It is the resource id of the imageview that I initialized in the getView()
  • DisplayImageOptions: These are options that I have configured in the initGridView() method
  • ImageLoadingListener (Optional) :  This is the listener which can be used for controlling loading of image asynchronously.

public class ImageAdapter extends BaseAdapter {
		@Override
		public int getCount() {
			return imageUrls.size();
		}

		@Override
		public Object getItem(int position) {
			return null;
		}

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

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			final ImageView imageView;
			if (convertView == null) {
				imageView = (ImageView) getLayoutInflater().inflate(R.layout.item_grid_image, parent, false);
			} else {
				imageView = (ImageView) convertView;
			}

			imageLoader.displayImage(previewImageUrls.get(position), imageView, options);

			return imageView;
		}
	}

Complete MainActivity.java

package com.labs.binarywall.lazyloading;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import android.support.v7.app.ActionBarActivity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AdapterView.OnItemClickListener;

public class MainActivity extends ActionBarActivity {

	private List<String> imageUrls;
	private List<String> previewImageUrls;
	private List<String> downloads;
	private List<String> fav;
	private List<String> ids;
	private TextView noConnectTextView;
	private DisplayImageOptions options;
	private ImageAdapter imageAdapter;
	private GridView gridView;
	private TextView results;
	private DownloadPreviewImagesTask downloadPreviewImagesTask;
	private int page = 0;
	int count=0;
	 protected ImageLoader imageLoader = ImageLoader.getInstance();
	private String gallery_url="http://walldroidhd.com/api.php?";
	ConnectionDetector cd;
	Boolean isInternetPresent = false;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		noConnectTextView = (TextView) findViewById(R.id.no_connect_message);

		cd = new ConnectionDetector(this);

		 results = (TextView) findViewById(R.id.tresults);

		 isInternetPresent = cd.isConnectingToInternet();

			// check for Internet status
			if (isInternetPresent) {
				imageUrls = new ArrayList<String>();
				previewImageUrls = new ArrayList<String>();
				downloads = new ArrayList<String>();
				fav = new ArrayList<String>();
				ids = new ArrayList<String>();

				//initializing gridview
				initGridView();

				//executing async task for getting data
			downloadPreviewImagesTask = getDownloadPreviewImagesTask(R.id.preview_img_loading, geturl());
			if (downloadPreviewImagesTask != null && downloadPreviewImagesTask.getStatus() == AsyncTask.Status.RUNNING) {
				downloadPreviewImagesTask.attachActivity(this);
				downloadPreviewImagesTask.setSpinnerVisible();

    		} else {
    			Log.d("downloadPreviewImagesTask", "downloadPreviewImagesTask has executed already");
    		}
		}
		else
		{

			showAlertDialog(MainActivity.this, "No Internet Connection",
					"You don't have internet connection!", false);
		}

	}

	@SuppressWarnings("deprecation")
	public void showAlertDialog(Context context, String title, String message, Boolean status) {
		AlertDialog alertDialog = new AlertDialog.Builder(context).create();

		// Setting Dialog Title
		alertDialog.setTitle(title);

		// Setting Dialog Message
		alertDialog.setMessage(message);

		// Setting OK Button
		alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				finish();
			}
		});

		// Showing Alert Message
		alertDialog.show();
	}

	public String geturl(){

		//You can set various URLs for different types of query here!
	    	return gallery_url;
	}

	private DownloadPreviewImagesTask getDownloadPreviewImagesTask(int spinnerID, String url) {
		downloadPreviewImagesTask = new DownloadPreviewImagesTask();
		downloadPreviewImagesTask.setSpinner(spinnerID);
		downloadPreviewImagesTask.attachActivity(this);
		downloadPreviewImagesTask.execute(url); // show spinner
		return downloadPreviewImagesTask;
	}

	class EndlessListListener implements OnScrollListener {

		private int visibleThreshold = 5;  //setting visible threshold for loading new data
        private int previousTotal = 0;
        private boolean loading = true;

		public void onScroll(AbsListView view, int firstVisible, int visibleCount, int totalCount) {

			if (loading) {
                if (totalCount > previousTotal) {
                    loading = false;
                    previousTotal = totalCount;
                }
            }

			//Log.i("check", String.valueOf(totalCount) + " " + String.valueOf(visibleCount) + " " + String.valueOf(firstVisible));
            if (!loading && (totalCount - visibleCount) <= (firstVisible + visibleThreshold)) {

            	Log.d("loadData", "new data loaded");
	    		String photosUri = ImageExtractor.PHOTOS_URI;

	    		String url = geturl() + photosUri.replace(ImageExtractor.FROM_REPLACEMENT, String.valueOf(page));;

	    		if (downloadPreviewImagesTask != null && downloadPreviewImagesTask.getStatus() == AsyncTask.Status.RUNNING) {
	    			Log.d("downloadPreviewImagesTask", "downloadPreviewImagesTask is running");
	    		}
	    		else  if(page<(count/21)) //checking if there is more data
	    		{
	    			downloadPreviewImagesTask = getDownloadPreviewImagesTask(R.id.new_photos_loading, url);
	    			Log.i("url", url);
	    			page = page+1;
	    		}
                loading = true;
            }

		}

		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {

		}

	}

	//Async Task for downloading new data
	class DownloadPreviewImagesTask extends AsyncTask<String, Void, ArrayList<HashMap<String, String>>> {

		private Exception exc = null;
		private ProgressBar spinner;
		private MainActivity imageGridActivity = null;
		private int spinnerId;

		public AsyncTask<String, Void, ArrayList<HashMap<String, String>>> setSpinner(int spinnerId) {
			this.spinnerId = spinnerId;
			return this;
		}

		public void attachActivity(MainActivity imageGridActivity) {
			this.imageGridActivity = imageGridActivity;
		}

		public void detachActivity() {
			this.imageGridActivity = null;
		}

		public ProgressBar getSpinner() {
			return spinner;
		}

		@Override
        protected ArrayList<HashMap<String, String>> doInBackground(String... params) {

            String url = "";
            if( params.length > 0 ){
            	url = params[0];
            }

            ImageExtractor imageExtractor = new ImageExtractor(url);
            ArrayList<HashMap<String, String>> images = null;
    		try {
    			images = imageExtractor.getImages();
    			 count = imageExtractor.getCount();

    		} catch (IOException e) {
    			exc = e;
    			Log.e("catched_error", e.getMessage(), e);
    		}
			return images;
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            setSpinnerVisible();
        }

        public void setSpinnerVisible() {
        	spinner = (ProgressBar) imageGridActivity.findViewById(spinnerId);
            spinner.setVisibility(View.VISIBLE);
        }

        @Override
        protected void onPostExecute(ArrayList<HashMap<String, String>> images) {
            super.onPostExecute(images);
            spinner.setVisibility(View.GONE);
            if (exc != null) {
            	imageGridActivity.showError(exc);
    		} else if (images !=null) {
    			imageGridActivity.initOriginalAndPreviewUrls(images);

    			if (gridView == null && imageAdapter == null) {
    				initGridView();
    			} else
    				imageGridActivity.getImageAdapter().notifyDataSetChanged();
    		}
    		else
    		{
    		results.setText("Sorry! No Results Found!");
    		}
        }
    }

	public ImageAdapter getImageAdapter() {
		return imageAdapter;
	}

	private void showError(Exception exc) {
		noConnectTextView.setText(exc.getMessage());
		noConnectTextView.setVisibility(View.VISIBLE);
	}

	private void initOriginalAndPreviewUrls(ArrayList<HashMap<String, String>> images) {

		for(int i=0;i<=images.size()-1;i++){

			String vurls=  images.get(i).get(ImageExtractor.ORIGINAL_IMAGES).toString();
			String purls=  images.get(i).get(ImageExtractor.PREVIEW_IMAGES).toString();
			String download=  images.get(i).get("downloads").toString();
			String ifav=  images.get(i).get("fav").toString();
			String ide=  images.get(i).get("id").toString();
			imageUrls.add(vurls);
			previewImageUrls.add(purls);
			downloads.add(download);
			fav.add(ifav);
			ids.add(ide);
		}

	}

	private void startImageGalleryActivity(int position) {
		Intent intent = new Intent(this, ImageDetails.class);
		intent.putExtra(ImageExtractor.IMAGES, (Serializable)imageUrls);
		intent.putExtra(ImageExtractor.PREVIEW_IMAGES, (Serializable)previewImageUrls);
		intent.putExtra(ImageExtractor.DOWNLOADS,(Serializable) downloads);
	    intent.putExtra(ImageExtractor.FAV, (Serializable) fav);
		intent.putExtra(ImageExtractor.IMAGE_POSITION, position);
		intent.putExtra(ImageExtractor.ID, (Serializable) ids);

		startActivity(intent);
	}

	private void initGridView() {
		gridView = (GridView) findViewById(R.id.gridview);
		imageAdapter = new ImageAdapter();
		gridView.setAdapter(imageAdapter);
		gridView.setOnItemClickListener(new OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
				startImageGalleryActivity(position);
			}
		});
		gridView.setOnScrollListener(new EndlessListListener());
	      options = new DisplayImageOptions.Builder()
			.showImageOnLoading(R.drawable.stub_image)
			.showImageForEmptyUri(R.drawable.image_for_empty_url)
			.showImageOnFail(R.drawable.image_for_empty_url)
			.cacheInMemory(true)
			.cacheOnDisk(true)
			.considerExifParams(true)
			.bitmapConfig(Bitmap.Config.RGB_565)
			.build();
	}

	@Override
	protected void onStop() {
		super.onStop();
	}

	public class ImageAdapter extends BaseAdapter {
		@Override
		public int getCount() {
			return imageUrls.size();
		}

		@Override
		public Object getItem(int position) {
			return null;
		}

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

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			final ImageView imageView;
			if (convertView == null) {
				imageView = (ImageView) getLayoutInflater().inflate(R.layout.item_grid_image, parent, false);
			} else {
				imageView = (ImageView) convertView;
			}

			imageLoader.displayImage(previewImageUrls.get(position), imageView, options);

			return imageView;
		}
	}

}

8. There is another activity ImageDetails.java which includes a Viewpager and is opened when the user clicks on grid item.

Complete ImageDetails.java Code


package com.labs.binarywall.lazyloading;

import java.util.List;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;

public class ImageDetails extends Activity {

	private ViewPager pager;

	private DisplayImageOptions options;
	 protected ImageLoader imageLoader = ImageLoader.getInstance();

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.ac_image_pager);

		//getting serialized data from MainActivity
		Bundle bundle = getIntent().getExtras();
		final List<String> imageUrls = bundle.getStringArrayList(ImageExtractor.IMAGES);
		final List<String> downloads = bundle.getStringArrayList(ImageExtractor.DOWNLOADS);
		final List<String> favs = bundle.getStringArrayList(ImageExtractor.FAV);

		int pagerPosition = bundle.getInt(ImageExtractor.IMAGE_POSITION, 0);

		//Setting DisplayImageOptions for UIL
	      options = new DisplayImageOptions.Builder()
				.showImageOnLoading(R.drawable.stub_image) //setting image to show on loading
				.showImageForEmptyUri(R.drawable.image_for_empty_url)
				.showImageOnFail(R.drawable.image_for_empty_url)
				.cacheInMemory(true)
				.cacheOnDisk(true)
				.considerExifParams(true)
				.bitmapConfig(Bitmap.Config.RGB_565)
				.build(); 

	    //initializing viewpager
		pager = (ViewPager) findViewById(R.id.pager);
		pager.setAdapter(new ImagePagerAdapter(imageUrls,downloads,favs));
		pager.setCurrentItem(pagerPosition);

	}

	@Override
	protected void onStop() {
		super.onStop();
	}

	//Adapter for Pager
	private class ImagePagerAdapter extends PagerAdapter {

		private List<String> images;
		private LayoutInflater inflater;
		private List<String> downloads;
		private List<String> favs;

		ImagePagerAdapter(List<String> images,List<String> d,List<String> f) {
			this.images = images;
			this.downloads=d;
			this.favs=f;
			inflater = LayoutInflater.from(ImageDetails.this);
		}

		@Override
		public void destroyItem(ViewGroup container, int position, Object object) {
			container.removeView((View) object);
		}

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

		@Override
		public Object instantiateItem(ViewGroup view, final int position) {
			final View imageLayout = inflater.inflate(R.layout.item_pager_image, view, false);
		final TouchImageView imageView = (TouchImageView) imageLayout.findViewById(R.id.image);
			final ProgressBar spinner = (ProgressBar) imageLayout.findViewById(R.id.loading);
			 TextView down = (TextView) imageLayout.findViewById(R.id.downloads);
			 TextView fav = (TextView) imageLayout.findViewById(R.id.Fav);

			down.setText(downloads.get(position) + " downloads");
			fav.setText(favs.get(position) + " liked");

			//displaying image using UIL
			imageLoader.displayImage(images.get(position), imageView, options, new SimpleImageLoadingListener() {
				@Override
				public void onLoadingStarted(String imageUri, View view) {
				spinner.setVisibility(View.VISIBLE);
				}
				@Override
				public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
				String message = null;
				switch (failReason.getType()) {
				case IO_ERROR:
				message = "Input/Output error";
				break;
				case DECODING_ERROR:
				message = "Image can't be decoded";
				break;
				case NETWORK_DENIED:
				message = "Downloads are denied";
				break;
				case OUT_OF_MEMORY:
				message = "Out Of Memory error";
				break;
				case UNKNOWN:
				message = "Unknown error";
				break;
				}
				Toast.makeText(ImageDetails.this, message, Toast.LENGTH_SHORT).show();
				spinner.setVisibility(View.GONE);
				}
				@Override
				public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {

					spinner.setVisibility(View.GONE);

				}
				});
			view.addView(imageLayout, 0);
				return imageLayout;

		}

		@Override
		public boolean isViewFromObject(View view, Object object) {
			return view.equals(object);
		}

		@Override
		public void restoreState(Parcelable state, ClassLoader loader) {
		}

		@Override
		public Parcelable saveState() {
			return null;
		}

	}
}

Screenshots

MainActivity

device-2014-10-24-222022

MainActivity: When the user scrolls to the bottom

device-2014-10-24-222050

ImageDetails Activity: Showing other data i.e. downloads and Favs

device-2014-10-24-222159

 

Download Code:

Download Project