Sending an HTTP request in a separate thread is a very common operation for almost every client-server web application. The idea is to spin-off a background thread that will send a request and process the response data, but won’t block the main (GUI) thread on the same time. At the end of its execution the background thread should use some kind of a callback mechanism to pass response results back to the main thread. Since this operation is very common I was expecting that pretty much every modern framework should have a lot of tutorials available online describing how to implement this functionality in a quick and easy way. However this was not the case for the BlackBerry Java API - after a couple of hours researching I still didn’t find anything that was ‘quick and easy’…
Finally, looking through RIM’s API docs I found a method Application.invokeLater() which can take a runnable object defined in a background thread and start it in the main thread. This method allowed me to pass execution control back to the main thread.
Here is a sample code:
First let’s create a ConnectionThread class that can take a URL (to send a request to) and call a method of some screen once the response is received (for the sake of simplicity we’ll be calling a method of the screen that is currently on top of the screen’s stack).
public class ConnectionThread extends Thread { String URL; public ConnectionThread(String URL) { this.URL = URL; //URL to send a request to } public void run() { //optional: show some popup "Please wait screen" final Screen dialogScreen = new WaitPopupScreen(); //wait popup screen extends RIM's PopupScreen class UiApplication.getUiApplication().invokeLater(new Runnable() { public void run() { UiApplication.getUiApplication().pushModalScreen(dialogScreen); } }); //send HTTP request and save the response HttpConnection connection = null; //use API 5.0 Connection factory class to get first available connection byte responseData[] = null; try { connection = (HttpConnection) new ConnectionFactory().getConnection(URL).getConnection(); int len = (int) connection.getLength(); responseData = new byte[len]; DataInputStream dis; dis = new DataInputStream(connection.openInputStream()); dis.readFully(responseData); } catch (Exception e) { // TODO Handle exception } final byte[] responseDataToProcess = responseData; //use invokeLater method to pass results back to the main thread UiApplication.getUiApplication().invokeLater(new Runnable() { public void run() { UiApplication.getUiApplication().popScreen(dialogScreen); //hide wait popup screen //pass results to the callback mathod of the current screen ((MyScreen)UiApplication.getUiApplication().getActiveScreen()).callBackMethod(responseDataToProcess); } }); } }This is the actual screen where we spin the connection thread off:
public class MyScreen extends MainScreen { public MyScreen() { super(); // add a button that will initiate an HTTP request ButtonField requestButton = new ButtonField("Send HTTP Request"); requestButton.setChangeListener(new FieldChangeListener() { public void fieldChanged(Field field, int context) { //start connectionThread on a button click new ConnectionThread("http://mnarinsky.com").start(); } }); add(requestButton); } //this method will be called from the connection thread public void callBackMethod(byte[] responseData){ //process response } }Finally this is a code for an optional 'Please wait' popup screen – displays a simple “Please wait..” text. If you want you can make it little more fancy by using some animated GIF image.
public class WaitPopupScreen extends PopupScreen { public WaitPopupScreen() { super(new VerticalFieldManager()); LabelField labelField = new LabelField("Please wait...", Field.FIELD_HCENTER); add(labelField); } }
30 comments:
Thank you so much! You're amazing.
awesome
Great post, Mike. Thanks!
post is quite good.But i have doubt on concepts of what is and when to use runnable and extends threads.
Can u help me in clearing this.
Not sure what you mean, but you can follow these links to read more about Thread class and Runnable interface:
http://www.javaworld.com/javaworld/jw-04-1996/jw-04-threads.html
http://download.oracle.com/javase/6/docs/api/java/lang/Thread.html
http://download.oracle.com/javase/6/docs/api/java/lang/Runnable.html
is this working ???
having a error in - new WaitPopupScreen();
give a advice for me ,i am a beginner..
What is your error?
Hey, great job.
Just tested. Just an error with missing try-catch but no problem.
Second error is:
Cant connect to google.com or any other website
tested with simulator (torch) and real torch
Errormessage on simulator: NegativeArraySizeException
any idea?
Me again.
Havnt started MDS. But have it key-signed and started in my real device. Just the waitpopup comes up. Nothing more.
Any idea? :)
Marvin, thanks.
I was aware of the missing try-catch block. I had it that way since I wanted the developer to implement error handling himself. I've changed it now though, in order to make this post's solution complete.
The second error you've seen happens because of an issue with sending requests specifically to www.google.com
I've updated the post's code, so you can try again - it should work now.
Well, you should not really see anything other than the Wait Popup, since the MyScreen.callBack() method is empty. Make sure that responseData array is getting populated with the response data. If it is, than it's working.
You can also add some logic to check response's HTTP code...
This is my callBack:
public void callBackMethod(byte[] response){
//process response
if(response != null && response.length > 0){
add(new LabelField(new String(response)));
}
}
In simulator works fine, but not on my real BB-Torch.
The app starts, i hit the button and on the right corner i see the transfer arrows and the waiting popup.
After a while (2mins) the popup get closed and no more happend.
Dont know why.
simulator is perfect, real device not Oo
Me again ;)
Have found my mistake.
on http://www.blackberry.com/knowledgecenterpublic/livelink.exe/fetch/2000/348583/800451/800563/What_Is_-_Different_ways_to_make_an_HTTP_or_socket_connection.html?nodeid=826935&vernum=0 is discribed how you make a connection.
I have tryed:
connection = (HttpConnection) new ConnectionFactory().getConnection(URL + ";deviceside=true").getConnection();
and it works ;)
Now my real device establishing a real TCP-connection - I think ;)
Marvin, the fact you had to add ";device=true" suggests that most likely you have an issue with either your BES or BIS connections on your device.
You also don't have to modify URL by yourself in order to specify the type of connection you want - that's what RIM's ConnectionFactory should do for you. I'm using ConnectionFactory object to get any available connection, but you can override this class to customize it for your needs.
More about ConnectionFactory class:
http://www.blackberry.com/developers/docs/5.0.0api/net/rim/device/api/io/transport/ConnectionFactory.html
http://supportforums.blackberry.com/t5/Java-Development/Sample-Code-Using-the-ConnectionFactory-class-in-a-BrowserField/ta-p/532860
Actually i have no BES-connection. Server down. Maybe thats the problem.
However, it works :)
Have you a solution to send a file via http (post) ?
Dont know how to get the bytearray of a file :)
Marvin, take a look a this thread:
http://stackoverflow.com/questions/5005648/blackberry-send-a-httppost-request/5085858#5085858
I have seen this site. But dont know, how ich get the bytearray of a file to send it :]
Something with:
FileConnection fc = (FileConnection)Connector.open("file:///" + file, Connector.READ);
And after?.. How can i read the file?
FileInputStream, maybe?
Thanks a lot.
I could get some help for my application from ur article
Thanks again.
Hi! I love your example! It's so difficult to find resources on BB app development..
But I get ClassCastException on this line:
((MyScreen)UiApplication.getUiApplication().getActiveScreen()).callBackMethod(responseDataToProcess);
No error if I comment out this line but it will not return the results due to that.
But I get ClassCastException on this line:
((MyScreen)UiApplication.getUiApplication().getActiveScreen()).callBackMethod(responseDataToProcess);
Make sure that your MainScreen class is called 'MyScreen'. Also you can set a breakpoint on that line and check in the debugger the type of the UiApplication.getUiApplication().getActiveScreen() object. This might give you a clue why your're getting the ClassCastException.
Please I am having an issue on the callback method I am trying to do this
Public void callBackMethod(byte[] responseData){
String val = new String(responseData);
If(val.equals(“no-user“)
{
Dialog.alert(“User does not exists “);
}
}
It throws errors on th Dialog display part please any ideas @ all.
Iyenemi, what is the error message?
can we sownload a full project of this post ?
thank you
http://pastebin.com/FQ1zCB7z
Mine isn't working, why ?
what do i import ?
what do i import ?
Nice post thanks dude
I am getting null pointer exception after waiting for about 2 minutes
can you help me please?
where i should put my paramater and value?
some animated GIF image.
This link is not working. Shows only a blank page. Please do the needful.
Post a Comment