Get the FULL version

Android: Creating a button to load images from a remote server

Android: Creating a button to load images from a remote server thumbnail

This is a follow up to the post published two days ago that explained how to load images into a View’s Canvas from different sources. Differently from the last post, I will explain how to load images from a remote server after the View has been loaded and rendered on the screen. Also, this post features how to download an image after pressing a button.

So, let’s get to it. The first thing the reader might be thinking is that we just need to create a button and place the code that downloads the image inside it. It’s not as simple as that, due to these problems:

  1. It is not possible to run the code that downloads the image on the same thread as the one the application is using.
  2. The thread can take a unknown amount of time to complete.
  3. The Canvas can’t be recycled (refreshed) after it was rendered on the screen (not without getting its surface holder).
  4. We can’t implement a OnClickListener interface object. Doing that would make it hard to create a thread from the implemented runnable interface.
  5. Even if the code worked (just by placing it inside the button) we need to somehow alert the user that the application will not respond until it finishes what it is doing.

Solutions for the first three problems were presented on the last post. These solutions will be similar to the ones that will be explained later in this post, but with some alterations required to make the button work as expected. Problem number 4 is the one that will require the greatest amount of changes in the code. It is a simple solution, although not necessarily obvious.

The last listed issue, isn’t required to make the application work from the code perspective, although it is not good to leave the user without any feedback. With that in mind, a progress dialog indicating that the application is downloading the image will be displayed.

So, here’s the code:

/*Omitted import declarations*/
 
/*Implement the Runnable interface for creating the thread and
 * the OnClickListener to start the thread inside the button*/ 
public class RemoteImagesExample extends Activity implements Runnable, OnClickListener
{
	//the handler object is created to update the UI thread from another one
	private Handler handler = new Handler();
 
	//a variable to store the downloaded Bitmap
	public Bitmap downloadedBitmap;
 
	//create a variable for the button
	private Button btn_download;
 
	//create a variable for the image in the View
	private ImageView img_downloaded;
 
	//create a progress dialog
	private ProgressDialog dialog;
 
	//a string array for the file name
	private String[] filepath;
 
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
 
		//set this view as the content of this Activity
        setContentView(R.layout.main);  
         
        //create a dialog
		dialog = new ProgressDialog(this);
		//set the title of the dialog
        dialog.setTitle("Downloading Image");
        //Set if the dialog can be skipped
        dialog.setCancelable(false);
        //Set if the dialog doesn't have a estimated time to be dismissed
        dialog.setIndeterminate(true);
        
        //get the button from the main.xml layout file
        btn_download = (Button) findViewById(R.id.btn_download);
 		
        //get the image from the main.xml layout file
		img_downloaded = (ImageView) findViewById(R.id.img_dowloaded);
         
		//set the button's OnClickListenter - which will be the one implemented on this class
		btn_download.setOnClickListener(this);
    }
     
    //Since this class implements the OnClickListener, the OnClick method must be overridden
	@Override
	public void onClick(View v)
	{
		//displays the dialog
		dialog.show();
		//starts a new thread that will execute all the code inside the run() method.
		new Thread(this).start();
	}
     
    //This method is responsible for downloading the image from a remote location
	private Bitmap DownloadBMP(String url) throws IOException
	{
		//create a URL object from the passed string
		URL location = new URL(url);
 		
		/*Get the name of the file and its path, and break it into different parts.
		 *Store each of these parts as elements in the filepath array.*/
		filepath = location.getFile().split("\u002F");
 		
		/*The last element of the filepath array will be the name of the file.
		 *Display the name in the progress dialog.*/
		dialog.setMessage("Downloading " + filepath[filepath.length-1]);
 		
		//create a InputStream object, to read data from a remote location
		InputStream input_s = location.openStream(); 
 		
		//use the BitmapFactory to decode the downloaded stream into a Bitmap
		Bitmap returnedBMP = BitmapFactory.decodeStream(input_s);
 		
		//close the InputStream
		input_s.close();
 		
		//returns the downloaded bitmap
		return returnedBMP;
	}
 
	//this method must be overridden, as we are implementing the Runnable interface
	@Override
	public void run() 
	{
		//Download the image
		try 
		{
			downloadedBitmap = DownloadBMP("http://img41.imageshack.us/img41/426/bricks.gif");
		} 
		catch (IOException e) 
		{
			//If the image couldn't be downloaded, use the standard 'image not found' bitmap
			downloadedBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.imagenotfound);
		}
 		
		//update the canvas from this thread using the Handler object
		handler.post(new Runnable() 
		{
			@Override
			public void run() 
			{
				//set the image on the View to display the downloaded Bitmap.
				img_downloaded.setImageBitmap(downloadedBitmap);
				
				//close the progress dialog.
				dialog.dismiss();
			}
		});	
	}
}

To make the thread and the button work, we need to implement the Runnable and the OnClickListener interfaces. The Runnable interface will be used later to define the code to be executed in the thread that is going to be created. The OnClickListener sets a listener for the only button on the View. As already explained, we could have created a OnClickListener instance object, but that would make it hard to access the Runnable interface that is required to create the thread.

Next, at the onCreate() method, variables are created to control the button, the image inside the View, the progress dialog and an array of strings to store the file path. The latter is used just to display the name of the file being downloaded at the progress dialog.

From line 35 through 41, the dialog settings are being adjusted, such as if it can be closed or if it is indeterminate. The rest of the onCreate() method makes the link between the View elements and their corresponding variables.

This is where all the code converges: at the onClick() method, that is being overridden because the OnClickListener interface has been implemented on this class. It is called every time the button is pressed, starting the progress dialog (line 58) and creating a new thread, (line 60) that executes the code inside the run() method. A new thread is created every time the button gets pressed, but don’t worry, they are destroyed as soon as the last line of the run() method is reached. You can see it for yourself in the DDMS Perspective, when using an emulated device and the Eclipse IDE.

Then, there is the DownloadBMP() method that starts by checking if the image file exists. If it does, the name of the image file is stored at the last element of the filepath array (line 71). And then, it’s set as the text in the progress dialog (line 75). After that, the image file is downloaded and decoded into a Bitmap (line 81).

Finally, there is the run() method that calls the DownloadBMP() method and uses a try/catch block to check if the image has been successfully downloaded. If anything goes wrong when downloading the image from the specified URL, a generic image is loaded from the Resources folder (line 102).

At line 106, the post() method from the handler object updates the application’s View, from the thread we created. It also closes the progress dialog (line 114).

The last thing to do is to add the following line to the Manifest file of the app, so it can access the Internet:

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

Add it just before the application tag.

UPDATE: ImageShack changed URL for the image mentioned on this post. Now, it can accessed by either the two following URLs:

  • http://imageshack.com/a/img291/426/bricks.gif   or
  • http://imageshack.com/a/img705/426/bricks.gif

That’s it! Here’s the source code:

Be the first to leave a comment!

Leave a Comment

Post Comments RSS