Blackberry WebBitmapField

March 13, 2008

The standard Blackberry API provides lots of useful UI components, known as fields. The BitmapField can be used to display an image. Recently I’ve needed to display images from the web, so I created the WebBitmapField class.

Anyone who’s done any Blackberry programming knows that getting data off the web can be a bit of a pain. It must be done in a background thread, which must then pass the result back to the UI thread. I’ve created a simple method called getWebData that handles all of this for me. The result is passed back to a WebDataCallback interface:

public interface WebDataCallback
{
	public void callback(String data);
}

getWebData is a static method in my Utils class:

public static void getWebData(final String url, final WebDataCallback callback) throws IOException
{
	Thread t = new Thread(new Runnable()
	{
		public void run()
		{
			HttpConnection connection = null;
			InputStream inputStream = null;

			try
			{
				connection = (HttpConnection) Connector.open(url, Connector.READ, true);
				inputStream = connection.openInputStream();
				byte[] responseData = new byte[10000];
				int length = 0;
				StringBuffer rawResponse = new StringBuffer();
				while (-1 != (length = inputStream.read(responseData)))
				{
					rawResponse.append(new String(responseData, 0, length));
				}
				int responseCode = connection.getResponseCode();
				if (responseCode != HttpConnection.HTTP_OK)
				{
					throw new IOException("HTTP response code: "
							+ responseCode);
				}

				final String result = rawResponse.toString();
				UiApplication.getUiApplication().invokeLater(new Runnable()
				{
					public void run()
					{
						callback.callback(result);
					}
				});
			}
			catch (final Exception ex)
			{
				UiApplication.getUiApplication().invokeLater(new Runnable()
				{
					public void run()
					{
						callback.callback("Exception (" + ex.getClass() + "): " + ex.getMessage());
					}
				});
			}
			finally
			{
				try
				{
					inputStream.close();
					inputStream = null;
					connection.close();
					connection = null;
				}
				catch(Exception e){}
			}
		}
	});
	t.start();
}

The WebBitmapField that makes use of the getWebData method is below. All you need to do is pass a URL to the constructor and it’ll load the image:

public class WebBitmapField extends BitmapField implements WebDataCallback
{
	private EncodedImage bitmap = null;

	public WebBitmapField(String url)
	{
		try
		{
			Util.getWebData(url, this);
		}
		catch (Exception e) {}
	}

	public Bitmap getBitmap()
	{
		if (bitmap == null) return null;
		return bitmap.getBitmap();
	}

	public void callback(final String data)
	{
		if (data.startsWith("Exception")) return;

		try
		{
			byte[] dataArray = data.getBytes();
			bitmap = EncodedImage.createEncodedImage(dataArray, 0,
					dataArray.length);
			setImage(bitmap);
		}
		catch (final Exception e){}
	}
}

Hopefully the WebBitmapField class will be of use to some Blackberry developers. Feel free to use it in your applications.

41 Comments »

  1. Great idea!

    Small suggestion:
    You could move the getWebData() method into the class WebBitmapField. Then you can execute the code of the WebBitmapField.callback method directly in the nested runnable and the byte[] -> String -> byte[] conversion is not necessary anymore.

    Comment by dlb — May 27, 2008 @ 3:35 am

  2. A very useful piece of code indeed. Thanks a lot !

    Comment by Raiyan — September 24, 2008 @ 2:48 pm

  3. Hello, Great snippet of code. I am just getting started with BB developing, can you please give an example of how to call this? Thanks so much.

    Comment by Nick — February 17, 2009 @ 10:19 pm

  4. Hi Nick. It extends BitmapField, so you can add it to your screen in the same way you’d add any other field.

    Field image = new WebBitmapField(“http://my.url/img.jpg”);
    add(image);

    Comment by Ben — February 18, 2009 @ 9:10 am

  5. Thanks for your reply Ben. I think I’m hung up on putting the code into a class. I put the above code in a Util class, then from my HelloWorld I try to do the following code:

    Field image = new WebBitmapField(“http://tk2.stb.s-msn.com/i/6D/9E151D7B6E2F72F99238977E266C95.jpg”);

    and i get the following error:

    C:\Program Files\Research In Motion\BlackBerry JDE 4.6.0\bin\SalutationScreen.java:34: cannot find symbol
    symbol : class WebBitmapField
    location: class SalutationScreen
    Field image = new WebBitmapField(“http://tk2.stb.s-msn.com/i/EC/B79ECC5486EA7937A329620AFD44.jpg”);
    ^
    1 error

    Thanks so much for your help

    Comment by Nick — February 18, 2009 @ 3:31 pm

  6. The WebBitmapField is a class, and needs to go in a file called WebBitmapField.java. The getWebData method needs to do into a class called Util, in a file called Util.java. Hope that helps!

    Comment by Ben — February 18, 2009 @ 6:20 pm

  7. Where does the WebDataCallback go?

    Comment by Nick — February 18, 2009 @ 8:04 pm

  8. In WebDataCallback.java

    Comment by Ben — February 19, 2009 @ 12:57 pm

  9. I have a problem with this code. I dont know why but it doesnt display any image, it just display a blank page. The strange is that it seems like the application is connecting to the site, but somewhere fails. Can you help me, do you know whats wrong?

    Comment by Sergio — March 3, 2009 @ 6:48 pm

  10. any chance you can send a “helloworld” like application that just get a bit map off the web and display’s it

    Thanks

    Comment by Arthur — April 8, 2009 @ 6:46 pm

  11. Hi Arthur – sure, I’ll put it up as my next post.

    Comment by Ben — April 12, 2009 @ 6:56 pm

  12. Field image = new WebBitmapField(”http://my.url/img.jpg”);
    add(image);

    Doesn’t work. I’ve created Util.java (do I need to import it? I have no idea how), WebDataCallback.java and WebBitmapField.java, but as soon as I type WebBitmapField it claims it’s not a class at all. How do I import the files correctly? BlackBerry development is such a nightmare! D=

    Comment by lostgame — April 20, 2009 @ 5:08 pm

  13. You shouldn’t need to import it no, not unless you put it in a difference package to you main app.

    It seems that quite a few people are confused about how exactly to use this code so I’m going to do a new post soon that will include a complete application, and make use of this class. Hopefully that will clear things up!

    Comment by Ben — April 20, 2009 @ 9:17 pm

  14. That would be great – using online images in my application is pretty much the most critical feature next to it’s JSON integration, which seems to be just as much as a nightmare.

    There’s really no such thing as a ‘newbie’s guide to BlackBerry dev’ at all. >.<

    Comment by lostgame — April 22, 2009 @ 7:17 pm

  15. hi Ben, i’m having trouble too implementing the code even though i’ve tried a couple of times reading these posts, i’m running out of time, hope you can make the post soon, please.

    Comment by Eduardo — April 28, 2009 @ 2:57 pm

  16. Hey Ben,

    Great job! Question though. Calling getBitmap() can only be done AFTER threading is complete right? How would one go about doing this? E.g. WebBitmapField is being called fine, but using getBitmap() always returns null. How would i go about getting the bitmap?

    Comment by Andrew — May 13, 2009 @ 10:34 pm

  17. Hi Andrew,

    Sorry for the delay! You could poll in a separate thread (eg. while(field.getBitmap() == null) sleep(1);), but you’d be better off implementing a new callback mechanism, in the same way that the WebBitmapField gets notified about the Bitmap being loaded.

    Comment by Ben — May 18, 2009 @ 9:08 pm

  18. Hey Ben, thanks for responding. I tried and tried and tried implementing a new callback mechanism. But it either kept crashing or the getBitmap() call kept getting called before the download finished. Any way you can show me a code snippet?

    Comment by Andrew — May 21, 2009 @ 1:54 am

  19. Thank You very much for this!

    Comment by Urbo — May 26, 2009 @ 7:28 am

  20. @Andrew, the code in the post already contains callback code. The getWebData does a callback to the WebBitmapField.

    To achieve what you’re after you can modify the callback method in WebBitmapField to perform a callback on an object that wants to know when the image is loaded. Here is a snippet:

    WebBitmapField:
    public function registerCallback(ImageLoadedCallback c) {
    this.loadedCallback = c;
    }

    public void callback(final String data)
    {
    // … existing code here
    if(this.loadedCallback != null) {
    this.loadedCallback.callback();
    }
    }

    Comment by Ben — May 31, 2009 @ 6:34 pm

  21. Hi there,
    Good piece of code, works well.

    Except after calling setBitmap() the UI does not update.
    I even tried calling invalidate() and updateLayout() and all sorts fo other tricks buit the UI wont update until i scroll the list containgin the bitmap or focus on it.

    Any suggestions?

    Comment by Dek — June 11, 2009 @ 8:14 pm

  22. Hi Dek,

    When are you calling setBitmap? Accoring to the API docs setImage/Bitmap both call Field.fieldChangeNotify(), which should result in the field being redrawn. I’ve not had any redraw issues myself. What device is this on?

    Ben

    Comment by Ben — June 11, 2009 @ 8:23 pm

  23. Hi, Really would love to get this working but for some reason it’s not. I have even put a show image button on the page and it fires off

    case 1:
    try {
    Log.info(“this image path” + productImagePath);
    Field image = new WebBitmapField(productImagePath);
    add(image);
    return;
    }
    catch (Exception e) {
    Log.error(“!!! Problem invoking image.!!!: ” + e);
    }

    i am adding to a mainscreen class but i don’t think that’s the problem as i’m not getting any log messages i’ve put in each of the classes i made with your code. I just get a TNLF 0 in my blackberry event log, any suggestions?

    Comment by Brendan — August 21, 2009 @ 7:46 am

  24. Hi Brendan,

    Try adding some log statements to the catch blocks and see if anything shows up there. Let me know what you see.

    Thanks, Ben

    Comment by Ben — August 21, 2009 @ 12:48 pm

  25. Hi Ben got it sorted, I remembered a prob i came up against a couple of weeks ago on same app, my bb is a vodafone release but i’m on o2. The storms apn settings are hardcoded so i have do the following to get round it at the end of each url i build
    +

    “deviceside=true;apn=wap.o2.co.uk;tunnelauthusername=o2wap;tunnelauthpassword=password”

    Cheers for this, it works great, any idea how to get the image larger or even in an objectlistview?

    Comment by Brendan — August 21, 2009 @ 4:11 pm

  26. Does anyone know how to control the size of the image that is created with this code?

    Comment by Brendan — August 22, 2009 @ 7:31 pm

  27. Hi Ben,

    Im having a little trouble getting my bitmap to load is there any chance of getting a look at the sample that you were talking about in an earlier reply.

    thanks in advance

    Comment by kharn — October 7, 2009 @ 4:31 pm

  28. thanks a lot for code

    work well

    Comment by v1an — November 25, 2009 @ 4:14 am

  29. Thanks a lot working well for my app

    Comment by PraveenGoparaju — December 16, 2009 @ 4:56 am

  30. thanks a lot, works perfectly :)

    Comment by Naveed — December 16, 2009 @ 5:22 am

  31. For anyone planning to use this code, I would like to state that in my opinion, this is not production quality code. It is a sample at best. There are, I think, a number of holes in it, that the unwary will fall into including:

    a) The code should check the response code before reading the returned data, so this check:
    int responseCode = connection.getResponseCode();
    if (responseCode != HttpConnection.HTTP_OK)
    should be used before you bother attempting to read the data.

    b) The processing turns the bytes into a String – which is unnecessary, a waste of resources (including more Objects to clean up) and potentially corrupting when the data is binary data (such as a picture).

    c) the following line in the WebBitmapField class:
    if (data.startsWith(“Exception”)) return;
    will not correctly process the exception generated when a response code other than 200 is obtained (check the code).

    I think this is a useful sample, and I like the way that it implements an Observer Interface. However, the samples provided in the HttpConnection JavaDoc in the BlackBerry Api are just as useful and show additional features (like extracting the length of the returned data). Do not copy this code into your application and expect it to work.

    Comment by Peter Strange — December 17, 2009 @ 6:27 pm

  32. Hi Peter,

    Thanks for taking the time to suggest some improvements. Point (a) is definitely a change worth making.

    You’re right that when we’re dealing with images it doesn’t make a lot of sense to convert the binary data into a string, but the getWebData() method is general purpose, and when grabbing data from the web I’m usually after a string. If you’re taking this code and want to use it just for grabbing images then certainly it might be worth changing.

    As for point (c) I think you’re mistaken. Note that any exceptions thrown in the getWebData method are caught, and result in the “Exception…” string being returned. It isn’t the nicest way of handing the error, but if you know of a better way of passing that information to a callback I’d love to know.

    Thanks, Ben

    Comment by Ben — December 17, 2009 @ 7:30 pm

  33. Ben,

    Thanks for responding to my comments.

    Apologies, re point (c), I’ve checked and you are right. Readers, please ignore my comment. If I could edit that out, I would.

    Re (b) and (c), I have similar code, which implements an ‘error’ callback, in addition to a data callback. The data callback returns a byte array. The error call back returns a code indicating where the error occurred, a description and the Exception (if any).

    As noted, this is a good sample, and I have directed people this way. My comments were made as a result of a poster on a forum who I suspect used this code, without really understanding it, and then could not debug it when it failed. I wanted to make sure that people who used this realised that it was a sample and not a piece of robust production code.

    Just so that you are aware, logging is probably the biggest issue that I have with this code, that makes me say it is not production level code.

    Whether you make a connection or not can depend on the Service plan of the user and the connection suffix added to the URL. With http requests potentially being passed through proxy servers and gateways, the data you get back to a BlackBerry can depend on the connection method you have used and the Headers you and the Server have put on the http request and response. So when a piece of code like this fails in a customer situation, there has to be some way that the customer can easily provide the developer with information about what it was trying to do and where it has failed. There is only limited information available here (for example there is no logging of the headers returned or the data prior to it being processed).

    The fact that there is no logging in a sample is absolutely fine – logging will just complicate the explanation.

    I hope this is helpful, I’m sorry if my comments sounded a bit harsh, and also apologies once again for misreading a part of the code, which lead me to think it was faulty.

    Comment by Peter Strange — December 18, 2009 @ 11:49 pm

  34. I have this code working on simulator but not on device (bold 9000 for both), any ideas !!

    Comment by devao — March 23, 2010 @ 8:00 pm

  35. Hi Devao – Have you tried debugging it on the device, to see if you get any error messages?

    Comment by Ben — March 24, 2010 @ 12:35 pm

  36. i found it, i have only wifi on device and mds on simulator

    Comment by devao — March 24, 2010 @ 3:41 pm

  37. Hi Friends,
    I tried above code but code hangs on “inputStream = connection.openInputStream();” and after 2 min it return null. i dont have MDS but when i send other http request i am getting response.
    can any one tell what might be the reason.

    Thanks in advance

    Comment by Dev — May 19, 2010 @ 12:22 pm

  38. Hey guys problem solved just change
    this line

    connection = (HttpConnection) Connector.open(url, Connector.READ, true);

    to

    if (WLANInfo.getWLANState() == WLANInfo.WLAN_STATE_CONNECTED) {
    connection = (HttpConnection) Connector.open(url+ “;interface=wifi”,Connector.READ_WRITE,
    true);

    Comment by Dev — May 20, 2010 @ 9:28 am

  39. Hey guys problem solved just change
    this line

    connection = (HttpConnection) Connector.open(url, Connector.READ, true);

    to

    if (WLANInfo.getWLANState() == WLANInfo.WLAN_STATE_CONNECTED) {
    connection = (HttpConnection) Connector.open(url+ “;interface=wifi”,Connector.READ_WRITE,
    true);
    } else {
    connection = (HttpConnection) Connector.open(url+”;deviceside=true”, Connector.READ_WRITE,
    true);

    }

    Comment by Dev — May 20, 2010 @ 9:31 am

  40. hi,
    i works fine on my simulator, but does not work on device 9000. i make http connection to get any data from my device so i think problem is not relevant with connection.
    thanks in advance.

    Comment by ysnky — June 8, 2010 @ 6:22 pm

  41. Great work! Works perfectly!

    Comment by Blink — August 1, 2010 @ 5:57 am

RSS feed for comments on this post. TrackBack URL

Leave a comment