Skip to content

Welcome!

Search the categories on the right for examples.

If you can't find what you need, then leave a comment on the Requests page!

Happy coding everyone.

New Add me on Twitter to get tweets on Think Android's latest posts!

Congrats to Packt Publishing! 1000 Titles and Counting!

September 27, 2012

Hey everyone,

Just got an email from Packt Publishing with the exciting news that they’ve published their 1000th title! Pretty impressive no?

Any ways, to celebrate:

————————————————————————————————————————————————————-

Dear Customer,

Packt Publishing has come a long way since it published its first book in 2004, and is now one of the leading technical publishers, renowned among developers for its focused and practical books on a wide range of tools and technologies.

Packt has just published its 1000th book. You are invited to join us in celebrating this milestone with a gift. Access our library, PacktLib, for free for a week, and choose any of our eBooks to download and keep.

To make use of this offer, you simply need to go to www.packtpub.com and log into your account, or register for an account, between the 28th and 30th September.

At Packt, we really appreciate your support in helping us get this far, and hope that you will continue to enjoy our range of books.

Kind regards,

Packt Publishing

————————————————————————————————————————————————————-

So yes – check out the resources they have and go get your free e-book! I’m definitely going to redeem mine =)

And PS, for those who don’t know, I happened to publish my book with Packt so if you’re interested feel free to download my e-book at Android Database Programming.

Happy coding as usual!

- jwei

Passing Objects in Intents: Parcelables and More

September 25, 2012

Hey everyone,

For this post, I thought I’d revisit the topic of Intents. In the past I’ve talked about passing Intents in between classes with simple, primitive data (see Passing Information between Activities) – turns out it’s a relatively easy task, and a core concept within the Android framework. However, the much harder task is passing your own classes (objects) in between Activities, and moreover, potentially passing lists of objects.

In this post, I’ll go through an example of writing an object that implements the Parcelable interface.

Consider a scenario where you have a list of Venues (i.e. Restaurants, Hotels, Clubs, anything with fields for lat, long, name, address, etc). Your user makes a request and gets back a list of these Venues. You then want to pass these results to a new Activity – maybe it’s a mapping Activity (as it was in my case), but regardless the goal is to pass this list of Venues from Activity A to Activity B without having to make the same request twice.

The first step is defining your object. The object must implement the Parcelable interface. Why you might ask? Well let’s step back and think about what’s happening here. Within Android, much of the inter-process (inter-Activity) communication is done through light weight data structures known as Parcels. In fact, both Intents and Bundles (two objects you’ve probably encountered before…) implement this Parcelable interface.

But how does this inter-process communication work? An Android process first converts an object into a byte stream which is then sent to another process (i.e. another Activity). This second process then reads in the byte stream and converts it back into an object: this exchange is more commonly known as serialization or marshalling. But how do the two Activities know what to do to serialize and de-serialize your object? For primitives like ints, doubles, even Strings, serialization is trivial as these primitives are already in byte form. And so this is where the Parcelable interface comes in.

By implementing the Parcelable interface, you are essentially giving the OS instructions on how to serialize and de-serialize your object. Conceptually this may be difficult to picture, but luckily Android has made the code for this super simply – in fact you are only required to override a few methods. With that, let’s take a look at what exactly needs to be done in the implementation:

public class ParcelableVenue implements Parcelable {

	private double lat, lon;

	private String name, address;

	public ParcelableVenue(double lat, double lon, String name, String address) {
		this.lat = lat;
		this.lon = lon;
		this.name = name;
		this.address = address;
	}

	public ParcelableVenue(Parcel source) {
		// TODO implement
	}

	public GeoPoint getGeoPoint() {
		return new GeoPoint((int) (lat * 1e6), (int) (lon * 1e6));
	}

	public String getName() {
		return name;
	}

	public String getAddress() {
		return address;
	}

	@Override
	public int describeContents() {                     // OVERRIDE METHOD #1
		return 0;
	}

	@Override
	public void writeToParcel(Parcel dest, int flags) { // OVERRIDE METHOD #2
		dest.writeDouble(this.lat);
		dest.writeDouble(this.lon);
		dest.writeString(this.name);
		dest.writeString(this.address);
	}

	public static final Parcelable.Creator<ParcelableVenue> CREATOR = new Parcelable.Creator<ParcelableVenue>() {

		// TODO implement

	};

}

So far we have the basic structure of our ParcelableVenue object – it has a few simple fields as well as some standard getters; nothing special there. You’ll then notice that there are two methods we need to override. The first is the describeContents() method. Typically returning 0 suffices unless you have numerous parcelable objects and require special serialization for some. The method itself is meant to return a bit mask that identifies the serialized object. In my case, I just return 0.

The second method is the writeToParcel(Parcel dest, int flags) method. The meat of the conversion happens here. In this method you are passed a destination Parcel which is eventually serialized and sent to the end process. Thus you simply need to write your object’s data into this parcel. Luckily, some simple write methods are given to you, such as writeDouble(), writeString(), writeInt(), writeIntArray(), etc. The flags parameter simply tells the writeToParcel() method how the object should be written.

Once these two methods are overridden, every class that implements the Parcelable interface then needs to have a static Parcelable.Creator object named CREATOR. Let’s step back one more time – where are we at this point? Well so far we’ve flattened the object and written it to a Parcel object. Our object is in essence nothing but a byte stream now, so the only thing that’s left to do is un-flatten it and convert it back into an object using this CREATOR object!

Our creator object is pretty simple and need only look like:

public class ParcelableVenue implements Parcelable {

	private double lat, lon;

	private String name, address;

	public ParcelableVenue(double lat, double lon, String name, String address) {
		this.lat = lat;
		this.lon = lon;
		this.name = name;
		this.address = address;
	}

	public ParcelableVenue(Parcel source) {
		// TODO implement
	}

	// ...

	@Override
	public void writeToParcel(Parcel dest, int flags) {
		dest.writeDouble(this.lat);
		dest.writeDouble(this.lon);
		dest.writeString(this.name);
		dest.writeString(this.address);
	}

	public static final Parcelable.Creator<ParcelableVenue> CREATOR = new Parcelable.Creator<ParcelableVenue>() {

		@Override
		public ParcelableVenue createFromParcel(Parcel source) {
			return new ParcelableVenue(source); // RECREATE VENUE GIVEN SOURCE
		}

		@Override
		public ParcelableVenue[] newArray(int size) {
			return new ParcelableVenue[size]; // CREATING AN ARRAY OF VENUES
		}

	};

}

And so we see that the very last step is simply to write another constructor for our ParcelableVenue class which initializes an object given a Parcel. This can be done with:

public class ParcelableVenue implements Parcelable {

	private double lat, lon;

	private String name, address;

	// ...

	public ParcelableVenue(Parcel source) {
		this.lat = source.readDouble();
		this.lon = source.readDouble();
		this.name = source.readString();
		this.address = source.readString();
	}

	// ...

}

The order here is important – the first double read will be the first double written as, again, it is a byte stream.

And that’s it! Once we have our ParcelableVenue, we can then do things like:

public class ClubsListActivity extends ListActivity {

	private List<Club> clubs;

	private Button mapViewButton;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.club_list);

		mapViewButton = (Button) findViewById(R.id.switch_map_view);
		mapViewButton.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				// PREPARE NEW INTENT TO SEND TO MAP ACTIVITY
				Intent i = new Intent(ClubsListActivity.this, VenueMapView.class);
				
				// INITIALIZE NEW ARRAYLIST AND POPULATE
				ArrayList<ParcelableVenue> overlays = new ArrayList<ParcelableVenue>();
				for (Club c : clubs) {
					overlays.add(new ParcelableVenue(c.getLat(), c.getLon(), c.getName(), c.getAddress()));
				}
				
				// EMBED INTO INTENT
				i.putParcelableArrayListExtra("venues", overlays);
				startActivity(i);
			}

		});

		SharedPreferences sp = getSharedPreferences(Constants.DB, Context.MODE_PRIVATE);
		double lat = (double) sp.getFloat(Constants.SP_PREV_LAT, (float) Constants.DEFAULT_LAT);
		double lon = (double) sp.getFloat(Constants.SP_PREV_LON, (float) Constants.DEFAULT_LON);

		// GET CLUBS NEAR YOU WITH LAT LON
	}
}

The receiving side then looks like:

public class VenueMapView extends MapActivity {

	private MapView map;

	private List<ParcelableVenue> venues;

	@Override
	protected void onCreate(Bundle icicle) {
		super.onCreate(icicle);
		setContentView(R.layout.mapview);

		SharedPreferences sp = getSharedPreferences(Constants.DB, Context.MODE_PRIVATE);
		double lat = (double) sp.getFloat(Constants.SP_PREV_LAT, (float) Constants.DEFAULT_LAT);
		double lon = (double) sp.getFloat(Constants.SP_PREV_LON, (float) Constants.DEFAULT_LON);

		map = (MapView) findViewById(R.id.venue_map); // INIT MAP
		map.setBuiltInZoomControls(true); // SET MAP CONFIGURATIONS
		map.getController().setCenter(new GeoPoint((int) (lat * 1e6), (int) (lon * 1e6)));
		int maxZoom = map.getMaxZoomLevel();
		map.getController().setZoom(maxZoom - 3);

		Intent i = getIntent(); // RETRIEVE OUR INTENT
		venues = i.getParcelableArrayListExtra("venues"); // GET PARCELABLE VENUES

		List<Overlay> mapOverlays = map.getOverlays();
		Drawable icon = this.getResources().getDrawable(R.drawable.good_pin);

		VenueOverlay vo = new VenueOverlay(icon, this); // INIT OVERLAY MARKERS
		List<OverlayItem> points = new ArrayList<OverlayItem>();
		for (ParcelableVenue v : venues) {
			OverlayItem o = new OverlayItem(v.getGeoPoint(), v.getName(), v.getAddress());
			points.add(o);
		}

		vo.addOverlayList(points);

		// ADD VENUE OVERLAYS TO MAP
		mapOverlays.add(vo);
	}
}

I won’t say too much about what I’m doing with my Maps Activity – maybe I’ll save this for a future tutorial. The important thing to see is how I send a list of ParcelableVenue objects, and then retrieve them on the other side. As you can probably see by now, there’s nothing hard code wise when implementing a Parcelable object. The difficulty typically stems from an incomplete understanding of how Android sends data from process to process – and this is understandable as almost all of this is abstracted away so that we only need to put simple values into Bundles and Parcels and voila things magically appear on the other side. But once you dig a little more into what’s happening behind the scenes, then all of this serialization and Parcelable implementation makes much more sense.

And with that I send you off again! Happy coding and hope this helped.

- jwei

Parsing JSON on Android

September 10, 2012

Hey everyone,

Now a days when people talk about transferring data through HTTP requests, two popular data formats come to mind: JSON and XML.

In earlier posts, I’ve written a little about XML (see ), but in this post I’m going to use another friendly library to show you guys a simple, efficient way to parse JSON. I’ll also talk a little about the merits of each data format, but the hope is by the end of this short post, you’ll have what you need to parse both XML and JSON.

The “library” I’m going to take advantage of is the JSON-Java library. It’s a really nice, self-contained library, which allows you to easily parse JSON. More recently, the library has even included methods that allow you to convert XML to JSON and vice versa – allowing you to better reuse your data parsers. Anyways, before reading this post, go ahead and download the library and add the source to your project. For me, I just add a new package (i.e. jwei.apps.json) and put the files in there.

Once you have that, the rest is quite simple. Recently I worked on a project where I had to make an HTTP request to get a list of clubs in NY. Each Club was simply an object that contained fields like name, address, lat/long, phone number, etc. The JSON structure of the HTTP response looked like:

[ // note that square brackets declare a json array
   { // while curly brackets declare a json object
      id: 11036,
      zip: "",
      lon: -73.9861709,
      address: "240 W 47th St,  New York, NY",
      name: "The Supper Club",
      number: "",
      url: "",
      lat: 40.7597637,
      country: "United States"
   },
   {
      id: 7034,
      zip: "10019",
      lon: -73.9863942,
      address: "251 W. 48th St,  New York, NY",
      name: "XVI",
      number: "",
      url: "",
      lat: 40.7607917,
      country: "United States"
   }
   .
   .
   .
]

To see for yourself, feel free to hit this URL:

http://djs-corner.appspot.com/getClosestClubs?lat=40.7600624&lon=-73.98558

Then, subsequently my JSON club parser looked like:

import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import jwei.apps.helpers.ConnectionManager;
import jwei.apps.json.JSONArray;
import jwei.apps.json.JSONObject;
import jwei.apps.types.Club;
import jwei.apps.types.Constants;

public class JsonParser {

	private static DefaultHttpClient httpClient = ConnectionManager.getClient();

	public static List<Club> getNearestClubs(double lat, double lon) {
		// YOUR URL GOES HERE
		String getUrl = Constants.BASE_URL + String.format("getClosestClubs?lat=%f&lon=%f", lat, lon);
		
		List<Club> ret = new ArrayList<Club>();
		
		HttpResponse response = null;
		HttpGet getMethod = new HttpGet(getUrl);
		try {
			response = httpClient.execute(getMethod);
			
			// CONVERT RESPONSE TO STRING
			String result = EntityUtils.toString(response.getEntity());
			
			// CONVERT RESPONSE STRING TO JSON ARRAY
			JSONArray ja = new JSONArray(result);

			// ITERATE THROUGH AND RETRIEVE CLUB FIELDS
			int n = ja.length();
			for (int i = 0; i < n; i++) {
				// GET INDIVIDUAL JSON OBJECT FROM JSON ARRAY
				JSONObject jo = ja.getJSONObject(i);
				
				// RETRIEVE EACH JSON OBJECT'S FIELDS
				long id = jo.getLong("id");
				String name = jo.getString("name");
				String address = jo.getString("address");
				String country = jo.getString("country");
				String zip = jo.getString("zip");
				double clat = jo.getDouble("lat");
				double clon = jo.getDouble("lon");
				String url = jo.getString("url");
				String number = jo.getString("number");
				
				// CONVERT DATA FIELDS TO CLUB OBJECT
				Club c = new Club(id, name, address, country, zip, clat, clon, url, number);
				ret.add(c);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		// RETURN LIST OF CLUBS
		return ret;
	}

}

Again, it’s relatively straight forward, but the methods I’ll make special note of are:

JSONArray ja = new JSONArray(result);
JSONObject jo = ja.getJSONObject(i);
long id = jo.getLong("id");
String name = jo.getString("name");
double clat = jo.getDouble("lat");

In the first method, we pass in a String which has JSON format. The constructor of JSONArray expects a String that has valid JSON array formatting and parses it accordingly – throwing a JSONException otherwise. Once we have our JSON array, it works similarly to a vector (ArrayList) and we can pass it an index and retrieve a single JSONObject. For each JSONObject, we can then retrieve various fields by type (i.e. getLong, getDouble, getString, etc). Pretty simple no?

As mentioned before, to allow for better re-usability of data parsers, the writers of JSON-Java extended the library’s functionality to include XML conversion, which is simply:

String xml = ""; // YOUR XML STRING
JSONObject jo = XML.toJSONObject(xml);
String name = jo.getString("name");
...

Thus, if we wanted to ensure maximum compatibility, we could have extended our parser to look like:


public class JsonParser {

	private static DefaultHttpClient httpClient = ConnectionManager.getClient();

	public static List<Club> getNearestClubs(double lat, double lon, String format) {
		// YOUR URL GOES HERE - OFTEN API REQUESTS WILL HAVE A FORMAT PARAM
		String getUrl = Constants.BASE_URL + String.format("getClosestClubs?lat=%f&lon=%f&format=%s", lat, lon, format);
		
		List<Club> ret = new ArrayList<Club>();
		
		HttpResponse response = null;
		HttpGet getMethod = new HttpGet(getUrl);
		try {
			response = httpClient.execute(getMethod);
			
			// CONVERT RESPONSE TO STRING
			String result = EntityUtils.toString(response.getEntity());
			
			// CONVERT RESPONSE STRING TO JSON ARRAY
			JSONArray ja;
			if(format.equalsIgnoreCase("xml")) {
				JSONObject xjo = XML.toJSONObject(result);
				ja = xjo.getJSONArray("clubs");
			} else if (format.equalsIgnoreCase("json")) {
				ja = new JSONArray(result);
			}

			// ITERATE THROUGH AND RETRIEVE CLUB FIELDS
			...
		} catch (Exception e) {
			e.printStackTrace();
		}

		// RETURN LIST OF CLUBS
		return ret;
	}

}

And voila!

Now, as for a brief discussion on JSON vs. XML, I’ll note three points:

1) Performance – in terms of performance, JSON is the better option. This is true for both the actual transferring of your data, and also for the parsing of the data. This is due to the lower overhead needed for JSON formatting, which doesn’t require the rigid tree/node structure of XML.

2) Readability – this is a more subjective point and I’ve seen arguments for both sides. I personally think XML is the cleaner format for reading, but most browsers now a days (i.e. Chrome, Firefox) have JSON and XML display built in and both are nicely laid out. However, I will mention that for more “complex” data structures, XML tends to display it in a more intuitive fashion, which leads me to my third point.

3) Complexity – while JSON is designed for quick and lean data structures, XML is built to handle data structures of varying complexity and depth. Consider a data structure that has fields which are several layers deep (i.e. a University object which has a list of Class objects which each have a list of Student objects which each have a list of …). When this is the case, it can quickly become a parsing headache when you’re wading through JSONArray after JSONArray trying to access the correct JSONObject. Here, having an XML parser, especially one that is equipped with tree traversing languages like XPATH, can be an extremely powerful tool for pinpointing the precise nodes you want.

And so I’ll end the post with that. Hope this was comprehensive and helpful!

As always – happy coding.

- jwei

Granting Content Provider URI Permissions

August 7, 2012

Hey everyone,

The goal of this short example is to show you how you can further protect your application’s data by enforcing permissions upon your custom content provider. For those who have never seen a custom content provider implementation, I invite you to check out my post Writing Your own Content Provider before reading on.

The purpose of giving your content provider the ability to grant access to its data should be pretty clear. Suppose you want to protect the integrity of your content provider’s data. Then, for some users/applications you may want to give them both read and write permissions, while for others you may only want to give them read permission. A simple example of this is attachments in a mail application [taken from Google]:

Access to the mail should be protected by permissions, since this is sensitive user data. However, if a URI to an image attachment is given to an image viewer, that image viewer will not have permission to open the attachment since it has no reason to hold a permission to access all e-mail.

These permissions are for a specific content URI, and will last until the Activity that receives them is finished. In other words, when an application grants read and/or write permissions to another Activity, these permissions are temporary. Before moving on to some code, let me note that this implementation is different than declaring the android:readPermission and android:writePermission attributes as these tags specify specific applications and give those applications permanent read/write access. In this way, the method below which uses the android:grantUriPermissions tag a.k.a the sub-tag grant-uri-permission is much more dynamic and flexible.

So let’s see how all this is done. Adding permission requirements to your content provider is actually quite simple. Let’s extend my Notes ContentProvider from the above example and give it the ability to grant URI permissions. Say you want to give the user the ability to share his/her notes through various mail/social-media applications, but you want to protect the content of the note. Then, you’ll want to add the following to your AndroidManifest.xml file:

<provider android:name="jason.wei.apps.notes.providers.NotesContentProvider"
          android:authorities="jason.wei.apps.notes.providers.NotesContentProvider"
          android:grantUriPermission="true" <!-- for granting URI-based permissions throughout the entire provider -->
          <grant-uri-permission android:pathPattern="/notes/" /> <!-- for granting URI-based permissions to specific sub-branches of the provider -->
</provider>

What I mean by the distinction “permissions throughout the entire provider” versus “permissions to specific sub-branches of the provider” is that in the former, any URI with pattern:

“jason.wei.apps.notes.providers.NotesContentProvider/…”

Will have the ability to grant permissions, while in the second only URIs with pattern:

“jason.wei.apps.notes.providers.NotesContentProvider/notes/{id}”

can grant URI permissions (for those struggling with the concept of URIs, just think of this as giving your entire hard drive some desired property, versus only giving one directory in your hard drive that same property). Finally, as for how you actually go about granting such permissions through Intents, the below shows it done in a generic Activity:

public class NoteUriGrantActivity extends Activity {
	
	public static final String NOTE_ACTION_VIEW = "jason.wei.custom.intent.action.NOTE_VIEW";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		// ... code ...

		// IMPLICIT INTENT EXAMPLE
		Uri uri = Uri.parse("content://jason.wei.apps.notes.providers.NotesContentProvider/notes/1");
		Intent intent = new Intent();
		intent.setAction(NOTE_ACTION_VIEW); // SET CUSTOM INTENT ACTION
		intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // GRANT TEMPORARY READ PERMISSION
		intent.setData(uri);
		startActivity(intent); // SEND INTENT TO BE RESOLVED

		// EXPLICIT INTENT EXAMPLE
		grantUriPermission("jason.wei.apps.NotesReader", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
	}
}

And with that, in order to resolve our custom Intent, we simply need to write a separate Activity (note this Activity need not be defined within the same application as the ContentProvider) with correctly specified intent filter:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="jason.wei.apps.NotesReader"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".NoteReaderActivity" android:label="@string/app_name" >
            <intent-filter>
                <action android:name="jason.wei.custom.intent.action.NOTE_VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
    </application>
</manifest>

And voila! Now your NoteReaderActivity simply needs to call the getIntent() method to retrieve the content of the note! Architecturally, this process looks like:

Uri Permissions Architecture

Granting Uri Permissions Architecture

And once the NoteReaderActivity obtains the content of the note, it has the ability to layer on top any additional functionality (i.e. including pictures, videos, other media content) before ultimately sharing it with your friends.

Here, at the end of the day, everyone is happy. The NotePad application can allow other applications on the device to consume its content without fear of data corruption, while the NoteReader application can focus on non-content related issues such as UI, media attachments, group sharing, etc.

Hope this helps and happy coding!

- jwei

Book: Android Database Programming

June 19, 2012

Hey everyone!

First off – I just wanted to thank you all for making this blog what it is. It’s been great getting your feedback through comments and emails, and I hope this blog has helped you guys in your development ambitions as much as it has helped me!

I’m pleased to announce the publication of my first book, Android Database Programming. It was actually through this blog that publishing company Packt contacted me and asked me to write on the topic.

As for what the book covers, I’ll briefly summarize here:

  • Learn about lighter forms of local data storage such as SharedPreferences
  • Dive into SQLite databases and learn how to customize and extend them
  • Examine various SQLite queries to efficiently query for your data
  • Learn to safely expose your SQLite database to external applications
  • Understand how to bind your SQLite database to the user interface
  • Explore various external databases such as Google App Engine and learn how to store/query data on these external platforms
  • Learn to make network requests to both post and get data from your external data store
  • Learn to retrieve, parse, and/or cache the incoming web data on the Android application

It’s currently available for purchase in both print and eBook/Kindle, and can be purchased through the publisher’s website as well as through Amazon.

A sample copy of Chapter 2 is also available for free.



Book Cover

Hope you guys like it and thanks again for all the support. As always, happy coding.

- jwei

Lazy Loading Images: From URLs to ListViews

June 13, 2012

Hey everyone!

Recently while working on a project I was asked to lazy load a list of images (bitmaps) into a ListView. What made the task even trickier was the fact that each image had to be streamed from a given URL. I thought about downloading all of the images at the application’s startup Activity, and then dumping them into some kind of external SD card as a cache, but I was told that the images were subject to frequent change and that true caching would be difficult. The result is how I chose to implement the feature, and I thought I’d share it with everyone.

Now there’s no guarantee that the way I’m doing this is the “correct” or optimal way – in fact if you DO know the best way to lazy load images from URLs then please share – but the solution below works and uses a handful of Android classes and concepts, all of which I’ll note as I walk you through the solution.

Before I move onto the code, let’s conceptually think about how we’re going to make this feature work. In this example, let’s say we’re making an HTTP request to some external server that returns a list of Students. Each Student has a name and an image URL which is to be streamed, converted into a Bitmap, and then displayed in a ListView. Getting the list of Student objects is the easy part – but what’s the next step? One option is to loop through each Student, grab each Student’s image URL, convert the URL into a Bitmap, and then allow each Student to hold a reference to their Bitmap; at which point we can load the list like we normally would.

A very feasible solution – but depending on the size of your Student list. Let’s assume that each image takes between 0.5 to 1 seconds to load. Now, if your database only has 5-10 Students in it, then maybe this iterative solution may work. However, consider a database with 100s of Students – clearly this iterative solution won’t hold up in that case. What’s the better solution? Wouldn’t it be nice if we could parallelize the loading process?

That’s step one – thinking of a way to parallelize the process. Step two is to do all this loading and processing on separate background threads. This way the user can interact with the application, even while the images are loading. The last step, step three, is to make sure that each background thread can somehow communicate with the original ListAdapter and make sure that the list gets updated each time an image is successfully streamed.

With that, here’s the code.

First, let’s start with a basic Student object. Each Student has a picture that needs to be loaded, and each student holds a reference to both their image’s URL as well as their Bitmap (to be downloaded):


public class Student {

	private String name;

	private String imgUrl;

	private Bitmap image;

	private StudentAdapter sta;

	public Student(String name, String imgUrl) {
                this.name = name;
                this.imgUrl = imgUrl;

                // TO BE LOADED LATER - OR CAN SET TO A DEFAULT IMAGE
                this.image = null;
	}

	public String getName() {
		return name;
	}

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

	public String getImgUrl() {
		return imgUrl;
	}

	public void setImgUrl(String imgUrl) {
		this.imgUrl = imgUrl;
	}

        public Bitmap getImage() {
		return image;
	}

	public StudentAdapter getAdapter() {
		return sta;
	}

	public void setAdapter(StudentAdapter sta) {
		this.sta = sta;
	}

	public void loadImage(StudentAdapter sta) {
		// HOLD A REFERENCE TO THE ADAPTER
		this.sta = sta;
		if (imgUrl != null && !imgUrl.equals("")) {
			new ImageLoadTask().execute(imgUrl);
		}
	}

	// ASYNC TASK TO AVOID CHOKING UP UI THREAD
	private class ImageLoadTask extends AsyncTask<String, String, Bitmap> {

		@Override
		protected void onPreExecute() {
			Log.i("ImageLoadTask", "Loading image...");
		}

		// param[0] is img url
		protected Bitmap doInBackground(String... param) {
			Log.i("ImageLoadTask", "Attempting to load image URL: " + param[0]);
			try {
				Bitmap b = ImageService.getBitmapFromURLWithScale(param[0]);
				return b;
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}

		protected void onProgressUpdate(String... progress) {
			// NO OP
		}

		protected void onPostExecute(Bitmap ret) {
			if (ret != null) {
				Log.i("ImageLoadTask", "Successfully loaded " + name + " image");
				image = ret;
				if (sta != null) {
					// WHEN IMAGE IS LOADED NOTIFY THE ADAPTER
					sta.notifyDataSetChanged();
				}
			} else {
				Log.e("ImageLoadTask", "Failed to load " + name + " image");
			}
		}
	}

}

Alright so what’s going on here? Again, the Student object we have here is very simple – it has only a name, image URL, and Bitmap image associated with it. For now, the image will be null (or a default image can be used) but we choose to hold a reference to it so that once it is loaded, we can “cache” it and only have to load it once.

Then, let’s note two things. First, we see that each Student has a loadImage() method which initiates an AsyncTask that we created called ImageLoadTask. By containing the loading process within the Student object, we’ve found a natural way to address step one and parallelize the process. Now, each Student can independently kick off their image loading processes and independently handle the resulting Bitmap. Why do we use an AsyncTask? Well that’s to address step two above and make sure that each process is loaded in a background thread. By doing this, we guarantee that each Student manages their own image Bitmap, and without disturbing the main UI thread!

Furthermore, we see that each Student object holds a reference to a StudentAdapter, whose code will be shown next. This is our solution to step three from above. By holding a reference to the Adapter, we can make sure that once the Student’s image is loaded (through the ImageLoadTask), the Student can then tell the adapter to update itself using the Adapter’s notifyDataSetChanged() method.

Let’s take a quick look at how the StudentAdapter is defined:


public class StudentAdapter extends BaseAdapter {

	private LayoutInflater mInflater;

	private List items = new ArrayList();

	public StudentAdapter(Context context, List items) {
		mInflater = LayoutInflater.from(context);
		this.items = items;
	}

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

	public Student getItem(int position) {
		return items.get(position);
	}

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

	public View getView(int position, View convertView, ViewGroup parent) {
		ViewHolder holder;
		Student s = items.get(position);
		if (convertView == null) {
			convertView = mInflater.inflate(R.layout.row_layout, null);
			holder = new ViewHolder();
			holder.name = (TextView) convertView.findViewById(R.id.name);
			holder.image = (ImageView) convertView.findViewById(R.id.image);
			convertView.setTag(holder);
		} else {
			holder = (ViewHolder) convertView.getTag();
		}
		holder.name.setText(s.getName());
		if (s.getImage() != null) {
			holder.pic.setImageBitmap(s.getImage());
		} else {
                // MY DEFAULT IMAGE
			holder.pic.setImageResource(R.drawable.generic_profile_man);
		}
		return convertView;
	}

	static class ViewHolder {
		TextView name;

		ImageView pic;
	}

}

Pretty simple really and nothing much to say. Really you just have to note that in the getView() method, if the Student’s image is null (implying that the Student’s image hasn’t been loaded yet) then I set the image to be a default generic profile picture. And so let’s bring this all together by looking at how we’d set this up in an Activity class:


public class StudentListActivity extends ListActivity {

        private List students;

        private StudentAdapter sta;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.list);

                // GET YOUR STUDENTS
                students = //...

                // CREATE BASE ADAPTER
                sta = new StudentAdapter(StudentListActivity.this, students);

                // SET AS CURRENT LIST
                setListAdapter(sta);

                for (Student s : students) {
                        // START LOADING IMAGES FOR EACH STUDENT
                        s.loadImage(sta);
                }
        }

}

And voila! That’s it! We look through our list of Students, and for each Student we call their loadImage() method, making sure we pass in the instantiated ListAdapter. Then, each loadImage() method will kick off an AsyncTask that will stream the image, cache it as a Bitmap, and then notify the ListAdapter to update itself.

How this is going to look at the end will be a list that the user can scroll through, where each row is loading itself behind the scenes, and displaying its image as soon as the loading is done. Hopefully this all makes sense – and again happy to hear other solutions as well! Best of luck and as usual, happy coding.

- jwei

2011 In Review

December 31, 2011

The WordPress.com stats helper monkeys prepared a 2011 annual report for this blog.

Here’s an excerpt:

London Olympic Stadium holds 80,000 people. This blog was viewed about 530,000 times in 2011. If it were competing at London Olympic Stadium, it would take about 7 sold-out events for that many people to see it.

Click here to see the complete report.

Follow

Get every new post delivered to your Inbox.

Join 870 other followers