Android: Download multiple files showing Progress Bar

21 April 2013 By Nithya Vasudevan 30,882 views 4 Comments
17 Flares Twitter 0 Facebook 8 Google+ 9 17 Flares ×

Project Description

  • In one of my previous tutorials, I explained how to download an image from URL in background and load it in ImageView using android.os.AsyncTask.
  • In this example, we will see how to download multiple files from URL showing download progression bar using Android progress dialog.
  • We will show Android progress bar which runs while the app downloads images from the web. Once all the images are downloaded, we will display the list of images. This example can be modified to work with any file type.
  • This example uses Android custom adapter for ListView with ImageView as its item.

Applications targeting the Honeycomb (Android 3) SDK or higher should perform networking IO operations using AsyncTask and not in main event loop threads. Trying to do in main thread causes android.os.NetworkOnMainThreadException.

Output

Environment Used

Prerequisites

Android Project

Create an Android project and name it as ShowDownloadProgression.

strings.xml

Open res/values/strings.xml and replace it with following content.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">ShowDownloadProgression</string>
    <string name="hello_world">Hello world!</string>
    <string name="menu_settings">Settings</string>
    <string name="image">Image</string>    
</resources> 

styles.xml

res/values/styles.xml

Open res/values/styles.xml and replace it with following content.

<resources>
    <style name="AppBaseTheme" parent="android:Theme.Light">
    </style>

    <!-- Application theme. -->
    <style name="AppTheme" parent="AppBaseTheme">
    </style>
</resources> 

res/values-v11/styles.xml

<resources>
    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
        <!-- API 11 theme customizations can go here. -->
    </style>
</resources>

res/values-v14/styles.xml

<resources>
  <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
        <!-- API 14 theme customizations can go here. -->
    </style>
</resources>

XML layout files

This Android progress bar example uses two layout files; one for displaying the main layout containing the ListView to display list of images and other for defining the row layout for ListView item which is just an ImageView.

activity_main.xml

This application uses XML layout file (activity_main.xml) to display ListView of images.

Open activity_main.xml file in res/layout and copy the following content.

<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"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/imageList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout> 

list_item.xml

Create a new layout file list_item.xml in res/layout and copy the following content.

<?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="wrap_content"
    android:orientation="vertical"
    android:padding="10dp" >

    <ImageView
        android:id="@+id/thumbnail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/image" />

</LinearLayout>

This ListView row layout contains only an ImageView to display an image from URL.

Create Bean class

RowItem.java

Create a new Java class “RowItem.java” in package “com.theopentutorials.downloadprogress.beans” and copy the following code.

package com.theopentutorials.downloadprogress.beans;
import android.graphics.Bitmap;

public class RowItem {

	private Bitmap bitmapImage;
	
	public RowItem(Bitmap bitmapImage) {
		this.bitmapImage =  bitmapImage;
	}

	public Bitmap getBitmapImage() {
		return bitmapImage;
	}

	public void setBitmapImage(Bitmap bitmapImage) {
		this.bitmapImage = bitmapImage;
	}		
}

This class is used to store one ListView row item which in this case is a Bitmap object.

Create Android Custom Adapter class for ListView

Create a new Java class “CustomListViewAdapter.java” in package “com.theopentutorials.downloadprogress.adapters” and copy the following code.

package com.theopentutorials.downloadprogress.adapters;

import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;

import com.theopentutorials.downloadprogress.R;
import com.theopentutorials.downloadprogress.beans.RowItem;

public class CustomListViewAdapter extends BaseAdapter {

	Context context;
	List<RowItem> rowItems;

	public CustomListViewAdapter(Context context, List<RowItem> items) {
		this.context = context;
		this.rowItems = items;
	}

	private class ViewHolder {
		ImageView imageView;
	}

	public int getCount() {
		return rowItems.size();
	}

	public Object getItem(int position) {
		return rowItems.get(position);
	}

	public long getItemId(int position) {
		return 0;
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder = null;

		LayoutInflater mInflater = (LayoutInflater) context
				.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
		if (convertView == null) {
			convertView = mInflater.inflate(R.layout.list_item, null);
			holder = new ViewHolder();
			holder.imageView = (ImageView) convertView
					.findViewById(R.id.thumbnail);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}

		RowItem rowItem = (RowItem) getItem(position);
		holder.imageView.setImageBitmap(rowItem.getBitmapImage());

		return convertView;
	}
}

This class uses BaseAdapter for creating custom adapter for ListView.

FileUtils class

In src folder, create a new Class and name it as “FileUtils” in the package “com.theopentutorials.downloadprogress.utils” and copy the following code.

This class provides facilities for closing a particular input/output stream. We use this Java utility class in our Activity class.

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class FileUtils {

	public static void close(InputStream stream) {
		if(stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void close(OutputStream stream) {
		if(stream != null) {
			try {
				stream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

Activity

In src folder, create a new Class and name it as “MainActivity” in the package “com.theopentutorials.downloadprogress” and copy the following code.

From Android 3.x Honeycomb or later, you cannot perform Network IO on the UI thread and doing this throws android.os.NetworkOnMainThreadException. You must use Asynctask instead as shown below.

package com.theopentutorials.downloadprogress;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.widget.ListView;

import com.theopentutorials.downloadprogress.adapters.CustomListViewAdapter;
import com.theopentutorials.downloadprogress.beans.RowItem;
import com.theopentutorials.downloadprogress.utils.FileUtils;

public class MainActivity extends Activity {

	ProgressDialog progressDialog;
 	CustomListViewAdapter listViewAdapter;
 	ListView listView;
 	
 	public static final String URL = 
		"http://theopentutorials.com/wp-content/themes/theopentutorials/images/open_tutorials_logo_v4.png";
 	public static final String URL1 = 
		"http://theopentutorials.com/wp-content/themes/theopentutorials/images/logo.jpg";
 	public static final String URL2 = 
		"http://theopentutorials.com/wp-content/themes/theopentutorials/images/open_tutorials_logo_v4.png";
  	@Override
	protected void onCreate(Bundle savedInstanceState) {
        	super.onCreate(savedInstanceState);
	        setContentView(R.layout.activity_main);
        	listView = (ListView) findViewById(R.id.imageList);
       		
		/*Creating and executing background task*/
		GetXMLTask task = new GetXMLTask(this);
		task.execute(new String[] { URL, URL1, URL2 });
		
		progressDialog = new ProgressDialog(this);
		progressDialog.setTitle("In progress...");
		progressDialog.setMessage("Loading...");
		progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		progressDialog.setIndeterminate(false);   
		progressDialog.setMax(100);
		progressDialog.setIcon(R.drawable.arrow_stop_down);
		progressDialog.setCancelable(true);
		progressDialog.show();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
    
    private class GetXMLTask extends AsyncTask<String, Integer, List<RowItem>> {
    	private Activity context;
    	List<RowItem> rowItems;
    	int noOfURLs;
		public GetXMLTask(Activity context) {
			this.context = context;
		}
		
		@Override
		protected List<RowItem> doInBackground(String... urls) {
			noOfURLs = urls.length;
			rowItems = new ArrayList<RowItem>();
			Bitmap map = null;
			for (String url : urls) {
				map = downloadImage(url);
				rowItems.add(new RowItem(map));
			}
			return rowItems;			
		}
		
		private Bitmap downloadImage(String urlString) {
			
			int count = 0;
			Bitmap bitmap = null;

			URL url;
			InputStream inputStream = null;
			BufferedOutputStream outputStream = null;
			
			try {
				url = new URL(urlString);
				URLConnection connection = url.openConnection();
		        int lenghtOfFile = connection.getContentLength();
		       
		        inputStream = new BufferedInputStream(url.openStream());
		        ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
		      
		        outputStream = new BufferedOutputStream(dataStream);
		        
		        byte data[] = new byte[512];        
		        long total = 0;

		        while ((count = inputStream.read(data)) != -1) {
		            total += count;
		            /*publishing progress update on UI thread.
		            Invokes onProgressUpdate()*/
		            publishProgress((int)((total*100)/lenghtOfFile));

		            // writing data to byte array stream
		            outputStream.write(data, 0, count);
		        }
		        outputStream.flush();
				
				BitmapFactory.Options bmOptions = new BitmapFactory.Options();
				bmOptions.inSampleSize = 1;
				
				byte[] bytes = dataStream.toByteArray();
		        bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length,bmOptions);

			} catch (MalformedURLException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				FileUtils.close(inputStream);
				FileUtils.close(outputStream);
			}
			return bitmap;
		}
		
		protected void onProgressUpdate(Integer... progress) {
			progressDialog.setProgress(progress[0]);
			if(rowItems != null) {
				progressDialog.setMessage("Loading " + (rowItems.size()+1) + "/" + noOfURLs);
			}
	   }
		
	   @Override
	   protected void onPostExecute(List<RowItem> rowItems) {
		listViewAdapter = new CustomListViewAdapter(context, rowItems);
		listView.setAdapter(listViewAdapter);
		progressDialog.dismiss();
	   }	
	 
	}
}
  • Whenever we need to perform lengthy operation or any background operation we can use Asyntask which executes a task in background and publish results on the UI thread without having to manipulate threads and/or handlers.
  • In onCreate(), we create a Android ProgressDialog. We also create and execute the task which downloads and creates Bitmap image files and stores it in RowItem class.
  • In doInBackground() we invoke publishProgress() which inturn invokes onProgressUpdate() to update the progress bar in UI thread.
  • Once the background computation finishes downloading all image files, onPostExecute() is invoked on the UI thread which creates and sets custom adapter for ListView.

AndroidManifest.xml

Define the activity in AndroidManifest.xml file. To access internet from Android application set the android.permission.INTERNET permission in manifest file as shown below.

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

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

    <!-- Allows applications to open network sockets. -->
    <uses-permission android:name="android.permission.INTERNET" />

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

Output

Run your application and you will get the output as shown in the beginning of this tutorial.

You can also modify the theme for Android progress dialog using styles.xml.
For example, in all styles.xml versions (values/styles.xml, values-v11/styles.xml and values-v14/styles.xml) change the following theme i.e.) android:Theme.Light as shown below.

<resources>
    <style name="AppBaseTheme" parent="android:Theme.Light">
        <!-- API 14 theme customizations can go here. -->
    </style>
</resources>

Project Folder Structure

The complete folder structure of this example is shown below.

Tags: , , , , , , , , ,

  • Luca Crisi

    Really interesting.

    But… do I really have to recreate all the project myself?
    How nice, if you could share the zipped project, too!

    Thank you anyway, for sharing your knowledge with us!

  • Luca Crisi

    I forgot to rate… 1000 stars for me if it works, I’ll give it a check, taking in account that I’m supporting older devices (2.3.3) – but this doesn’t seem to be a problem. I already have a working piece of code using AsyncTask (that only works well with ONE file), so, it should work well.

    Also, I’m not able to show the BYTES downloaded, instead of this stupid 20/100 at 20%.
    I’d dike to show something like 660 / 3300 bytes at 20%, instead. Any idea?

    Thank you again.

    • Hitendra Joshi

      @Luca Crisi

      on the place of public pogress – put this line
      publishProgress(total);

      protected void onProgressUpdate(Integer… progress) {
      progressDialog.setProgress(progress[0]);
      if(rowItems != null) {
      progressDialog.setMessage( lenghtoffile);
      }
      }

  • Annonamous

    Hey this was a fantastic tutorial. Don’t really know if your still active, but I was just wondering, is there a way to make it only show 1 single progressBar, instead of 3 distinct progressBar for each image? So for example rather than after 1 image is downloaded it goes to load 2/3 the single progressBar would be at around 33-34% (If we were downloading 3 images). Thanks in advance