ryac[PA]

ryac[PA]Download the app to your phone!

My online photo album has been made into an Android app. Look out folks, the ‘killer-app’ is out on the market.

hah.

Download the app to your phone!

Multi-threading images from the web into an Adapter

When working with ListViews or GridViews inside Android apps, you may need to load dynamic content, such as photos, from the web into these views. If you’re not multi-threading your UI will hang. This is because everything is running on a single thread and the latency of the network will make your app unresponsive. Most of the time, if you’re retrieving anything from the web it’s better to be multi-threading to keep your UI responsive.

GridViews and ListViews (among others) use Adapters to connect data to your views. There’s explanation on how to use Adapters inside a GridView on the Android Developer’s site. While this example is great for static content, you’ll run into problems when loading images from the web.

I’m currently building an Android app that’s pretty much my photoalbum I have on my site. I’m loading my thumbnails into a GridView as shown below and from here you can tap on a thumbnail to see a bigger version.

GridView Screen

Screenshot of my photoalbum app, the GridView loading external images

While the GridView example on the Android Developer’s site is great for loading static content, I needed to load images from the web, which means I should be multi-threading. I’ve done this inside my Adapter and then set my Adapter to my GridView. Below is my ImageAdapter class. I thought the best way to explain this would be explanations in the code itself, check it out..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package ca.ryac.photoalbum.adapters;
 
import ...
 
public class ImageAdapter extends BaseAdapter {
 
	private Context context;
 
	// stores all data for album..
	private ArrayList<HashMap> photos;
 
	// my folder I'm currently loading from..
	private String folder;
 
	// views stores all the loaded ImageViews linked to their
	// position in the GridView; hence  in
	// the HashMap..
	private HashMap views;
 
	public ImageAdapter (Context c, String f, ArrayList<HashMap> p) {
		context = c;
		// I need to hold a reference to my current album folder..
		folder = f;
		// photos stores all of my data for the album, this includes
		// the filename, some other stuff I'm not using..
		photos = p;
 
		// 'views' is a HashMap that holds all
 		// of the loaded ImageViews linked to their position
		// inside the GridView. it's used for checking to see if
		// the particular ImageView has already been loaded
		// (inside the getView method) and if not, creates the
		// new ImageView and stores it in the HashMap along with
		// its position.. 
		views = new HashMap();
	}
 
	@Override
	public int getCount() {
		return photos.size();
	}
 
	@Override
	public Object getItem(int position) {
		return position;
	}
 
	@Override
	public long getItemId(int position) {
		return position;
	}
 
	@Override
	public View getView(int position, View view, ViewGroup parent) {
 
		ImageView v;
 
		// get the ImageView for this position in the GridView..
		v = views.get(position);
 
		// this ImageView might not be created yet..
		if (v == null) {
			Log.d(Constants.TAG, "This view is not created. create it.");
 
			// create a new ImageView..
			v = new ImageView(context);
 
			v.setLayoutParams(new GridView.LayoutParams(110, 110));
			v.setPadding(10, 10, 10, 10);
 
			// I'm setting a default image here so that you don't
			// see black nothingness.. (just using an icon that
			// comes with the Android SDK)
			v.setImageResource(android.R.drawable.ic_menu_gallery);
 
			// get the filename that this ImageView will hold..
			String file = photos.get(position).get("file").toString();
 
			// pass this Bundle along to the LoadImage class,
			// which is a subclass of Android's utility class
			// AsyncTask. Whatever happens in this class is
			// on its own thread.. the Bundle passes
			// the file to load and the position the photo
			// should be placed in the GridView..
			Bundle b = new Bundle ();
			b.putString("file", file);
			b.putInt("pos", position);
 
			// just a test to make sure that the position and
			// file name are matching before and after the
			// image has loaded..
			Log.d(Constants.TAG, "*before: " + b.getInt("pos") + " | " + b.getString("file"));
 
			// this executes a new thread, passing along the file
			// to load and the position via the Bundle..
			new LoadImage().execute(b);
 
			// puts this new ImageView and position in the HashMap.
			views.put(position, v);
		}
 
		// return the view to the GridView..
		// at this point, the ImageView is only displaying the
		// default icon..
		return v;
 
	}
 
	// this is the class that handles the loading of images from the cloud
	// inside another thread, separate from the main UI thread..
	private class LoadImage extends AsyncTask {
 
		@Override
		protected Bundle doInBackground(Bundle... b) {
 
			// get the file that was passed from the bundle..
			String file = b[0].getString("file");
 
			// fetchPhoto is a helper method to get the photo..
			// returns a Bitmap which we'll place inside the
			// appropriate ImageView component..
			Bitmap bm = fetchPhoto(file);
 
			// now that we have the bitmap (bm), we'll
			// create another bundle to pass to 'onPostExecute'.
			// this is the method that is called at the end of 
			// our task. like a callback function..
			// this time, we're not passing the filename to this
			// method, but the actual bitmap, not forgetting to
			// pass the same position along..
			Bundle bundle = new Bundle();
			bundle.putParcelable("bm", bm);
			bundle.putInt("pos", b[0].getInt("pos"));
			bundle.putString("file", file); // this is only used for testing..
 
			return bundle;
		}
 
		@Override
		protected void onPostExecute(Bundle result) {
			super.onPostExecute(result);
 
			// just a test to make sure that the position and
			// file name are matching before and after the
			// image has loaded..
			Log.d(Constants.TAG, "*after: " + result.getInt("pos") + " | " + result.getString("file"));
 
			// here's where the photo gets put into the
			// appropriate ImageView. we're retrieving the
			// ImageView from the HashMap according to
			// the position..
			ImageView view = views.get(result.getInt("pos"));
 
			// then we set the bitmap into that view. and that's it.
			view.setImageBitmap((Bitmap) result.getParcelable("bm"));
		}
 
	}
 
	// this is a helper method to retrieve the photo from the cloud..
	private Bitmap fetchPhoto (String file) {
		Bitmap bm = null;
 
		String path = "http://www.path_to_photoalbums_here.ca/" + file;
 
		Log.d(Constants.TAG, "path: " + path);
 
		try {
 
			final HttpParams httpParameters = new BasicHttpParams();
			// Set the timeout in milliseconds until a connection is established.
			HttpConnectionParams.setConnectionTimeout(httpParameters, 7000);
 
			// Set the default socket timeout (SO_TIMEOUT)
			// in milliseconds which is the timeout for waiting for data.
			HttpConnectionParams.setSoTimeout(httpParameters, 10000);
 
			final HttpClient client = new DefaultHttpClient(httpParameters);
			final HttpResponse response = client.execute(new HttpGet(path));
			final HttpEntity entity = response.getEntity();
			final InputStream imageContentInputStream = entity.getContent();
 
			// wrapping the imageContentInputStream with FlushedInputStream.
			bm = BitmapFactory.decodeStream(new FlushedInputStream (imageContentInputStream));
 
		}
		catch (Exception e) {
			Log.e(Constants.TAG, e.getMessage(), e);
		}
 
		return bm;
	}
 
}

And inside my Activity class, I simply connect the Adapter to the GridView:

1
2
3
private void setAdapter() {
	grid.setAdapter(new ImageAdapter(this, folder, photos));
}

I did run into another problem with loading the images into my app, not only in my GridView but also when viewing the bigger version of the photo. The bitmap would not display and no obvious errors came up. Looking at the logcat I found this: “skia decoder->decode returned false“. I’ve read that this happens over slow connections but my connection seems pretty fast so after searching around I found a solution to the ‘decoder->decode returned false’ problem here. There are actually two solutions explained. Try both if the first method didn’t work for you – I was originally using the second method while getting the ‘decoder->decode returned false’ problem. I then tried the first method and that worked.

I hope this helps to shed some light on multi-threading images from the cloud into your Adapters for various Views. If you see areas of improvement please let me know!

Robotlegs / AS3 Signals Flickr Example

I’ve been spending some time with Robotlegs these days. I find it great to use and coming from PureMVC it was relatively easy to get the basics down.

What really helped me out a lot was Joel Hooks posts:

http://joelhooks.com/2011/03/12/an-introduction-to-robotlegs-as3-part-1-context-and-mediators/
http://joelhooks.com/2011/03/12/an-introduction-to-robotlegs-as3-part-2-models/
http://joelhooks.com/2011/03/12/an-introduction-to-robotlegs-as3-part-3-services/

And for working with Signals in RL:

http://joelhooks.com/2010/02/14/robotlegs-as3-signals-and-the-signalcommandmap-example/

I’ve created a simple app to help me understand the communication between mediators, controllers, and models/services. The app searches for photos on Flickr and presents them in a list, clicking on an item in the list loads the photo. I first tried this with using Events, then moved on to using Signals. You can download the source for this on gitub.

Let me know what you think!

10,000 Hours. How Long is That?

Malcolm Gladwell writes in his book, “Outliers”, about how long it takes to become really good at anything complex, from building a gigantic software company to becoming a legendary musician. Researchers say it’s 10,000 hours. How long is that? You can find out below..

Get Adobe Flash player

The Undocumented “addFrameScript” Method

There’s a useful function inside the MoveClip class (AS3) that will add script inside a Movieclip on a specific frame without actually having to add it manually to the timeline. This is great if, say you have someone working purely on animation and then someone else working on the interface to hold/play/control the animation. A lot of times this person will need to know when then animation has completed and will therefore need to add script inside the MovieClip animation. The addFrameScript can do this for you without altering the animation and re-exporting, etc.

The function takes two parameters: frame number to place the script (0 based) and the function name to call.

public function addFrameScript (frame:uint, notify:Function):void;

Example:

1
2
3
4
5
6
7
8
 
// animation is the name of the movieclip.. 
animation.addFrameScript (77, onAnimationEnd);      
 
// function called when playhead reaches 76.. 
private function onAnimationEnd ():void { 
	trace ("onAnimationEnd.."); 
}

StopWatch Class

I worked on a couple projects a while back that required a stopwatch (or timer) to keep track of time in a game, so I made this StopWatch class. Found it very helpful so I wanna share it. Besides the basic functions of starting, stopping, and resetting, you can also pass in a TextFormat object to change the formatting of the text, get the time in milliseconds (it’s what I used to store in the database), and parse the milliseconds back into something more readable..

Here’s how to use it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import ca.ryac.StopWatch;        
 
// creates a StopWatch obj and sets the formatting to the txtFormat obj.. 
// last parameter will set embeddedFonts to either true or false (default is false).. 
var sw:StopWatch = new StopWatch (txtFormat, true);        
 
// pretty self-explanatory.. 
sw.start (); 
sw.stop (); 
sw.reset ();        
 
// set or change the formatting and sets embeddedFonts to true.. 
sw.setTextFormat (txtFormat2, true);        
 
// output the time in milliseconds 
trace (sw.time);        
 
StopWatch.parseTime (87292); // returns 01:27:29

Download StopWatch.as

Japanese “Flash Card” Application for Flash Lite Devices

I started learning Japanese not too long ago and to help me with my studies I made a small app to familiarize myself with the Hiragana and Katakana characters. It proved to be very useful while waiting for the bus, taking the train, etc.

Here’s some screen shots:

jpfca_01 jpfca_02

You can choose to randomly display the characters for each character set and also show or hide names when the card is first displayed.

I’ve been testing on a Nokia E65 and works well. If you’ve seen this app before, you might have read my comment about the small bug that appeared in Flash Lite 2.x – how the first card was not matching the romanji name. The problem was not enough time was given to completely load the swf before calling some script. I’ve fixed this by giving more time (read as: inserting more frames) for the swf to load completely before calling any script on it. This way, the swf fully loads and is able to jump to the correct frame to match the romanji. So it works in FL 1.1, 2.x, and 3.0 now but I found it a bit strange how this problem only appeared in the newer FL versions (2.x & 3.0).

Please feel free to download it here and give it a try. If you’re studying Japanese I hope it helps!

To find out if your phone has Flash Lite pre-installed, download this pdf file originally available on Bill Perry‘s blog.