rite once, run anywhere. This Java motto seems to hold true even for mobile devices. Indeed, since it was born J2ME has had an ever-increasing impact on mobile development. Some may argue that J2ME is not as portable as other Java technologies, but even so, the result obtained on different mobile devices is, in most cases, still acceptable.
When thinking of a client/server application with a J2ME client, the first choice that comes to mind for the server-side implementation is J2EE. Of course, if you have a Web Server that supports J2EE then your choice is undoubtedly a good one. But what if you don’t have such an expensive Web server; instead, you have a low-cost domain that supports only PHP? In this case you have two options:
- Spend more money to obtain J2EE support.
- Write your server-side application using PHP.
This article discusses the second option of course, but there’s still something missing: What if you want to store any information transmitted by the client for later retrieval? You’ll need some type of persistent storage, and for most large applications, you’ll probably want to use a database rather than simple file or XML storage. I’ve selected MySQL for this article for the same reason I chose PHP?it’s inexpensive. Figure 1 shows the complete interaction between the client (J2ME), the server (PHP), and the database (MySQL).
Figure 2 shows the simple database schema used for the sample project. The downloadable code for this article contains a script you can use to setting up the database with MySQL.
|
|
Required Software
To run the samples for this article you’ll need the following software:
- Sun Java Wireless Toolkit. I used the 2.2 version.
- PHP
- Apache HTTP Server
- MySQL
Author’s Note: If you’re running Windows and haven’t already installed PHP, MySQL and Apache HTTP Server, you can use EasyPHP to install the aforementioned software in “one single shot.” EasyPHP also installs PHPMyAdmin which is a simple administrative application for MySQL. |
Building a Wireless Blog
Nowadays, many developers have their own blog. For this reason the sample project for this article is a mobile application that lets you update a multi-user blog from wherever you are. By “multi-user blog” I mean a blog that can be managed and updated by different users provided that they own an account within the blog. Because of this multi-use aspect, the application allows users to read posts created by other users with blog accounts. Figure 3, Figure 4, Figure 5, and Figure 6 should clarify the intent and overall functionality of the application.
|
|
|
|
I’ll dissect these figures one by one to help you gain an understanding of the client application’s functional requirements.
Figure 3 shows the main menu of the application. Basically, you can choose to:
- Write a new post and upload it to your server.
- Download a post from your server.
- Logout from the application.
When you first launch the program, you’ll see the Login form shown in Figure 4. The information users enter into this form is permanently stored on their mobile device using RMS (Record Management System) so users won’t see this form or need to login on subsequent launches. The application uses the login credentials to upload and download posts to and from the Web server. The startApp method checks whether the login data is already stored:
protected void startApp() { // check if the login info is already stored if (isLoginEmpty()) { setDisplay(fmLogin); } else { setDisplay(lsMain); } } ... private boolean isLoginEmpty() { RecordManager rm = new RecordManager("LOGIN"); return (rm.numRecords() < 2); }
The isLoginEmpty method checks the number of records present within the LOGIN record store. Delving into RMS and how to store data in your MIDP applications is beyond the scope of this article. All you need to know in this context is that you can store information in your mobile device using records. Each record contains a byte array. For the LOGIN record store I used the first record to store the username and the second for the password. Hence, if the number of records is less than two the login data hasn't yet been stored, so the application displays the Login form is displayed. The RecordManager class is simply a wrapper for the RMS-related classes. Of course, you can always reset the login information by selecting the Logout option.
Writing and Uploading Posts
Figure 5 shows the form used to write a post. To send a post you enter a title and the post text, then select the Upload command, which runs this code:
//retrieve the login data Login login = new Login(); //username String username = login.getUsername(); //password String password = login.getPassword(); //title String title = tfTitle.getString(); //text String text = tfText.getString(); //retrieve the post PostSender pr = new PostSender(midlet, this, username, password, title, text); pr.start();
This code retrieves the user's credentials plus the title and text of the post and sends this data to the server using a separate thread invoked by the PostSender class' start method. The core method of PostSender is sendPost:
private void sendPost() throws IOException { InputStream is = null; StringBuffer sb = null; HttpConnection http = null; try { // append the query string onto the URL URL += "?username=" + username + "&password=" + password + "&title=" + title + "&text=" + text; // replace chars not allowed in the URL URL = EncodeURL(URL); // establish the connection http = (HttpConnection) Connector.open(URL); // set the request method as GET http.setRequestMethod(HttpConnection.GET); // server response if (http.getResponseCode() == HttpConnection.HTTP_OK) { sb = new StringBuffer(); int ch; is = http.openInputStream(); while ((ch = is.read()) != -1) sb.append((char) ch); } else { System.out.println("Network error"); networkError(); } } catch (Exception e) { System.err.println("Error: " + e.toString()); networkError(e.toString()); } finally { if (is != null) is.close(); if (sb != null) result = sb.toString(); else networkError(); if (http != null) http.close(); } if (result != "") { System.out.println(result); midlet.showInfo(result, getCurrentDisplay()); } else { networkError(); } }
The sendPost method establishes a connection with the server, sends the data needed to store the post and waits for a response, which can be either a success or failure notification. The URL string for this example is http://localhost:8000/Blog/post_retriever.php, where 8000 is the port used for Apache Web Server and post_retriever is the PHP script responsible for storing the post data in the database. You'll see this in due course when analyzing the server-side application.
Author's Note: Be sure to change the URL shown in the preceding to match your settings.
![]() | |
Figure 7. Obtaining a post: Here's a downloaded post as displayed within a form on the device. |
Downloading Posts
I'm not going to examine the post-retrieval feature because the code is very similar to that just shown for sending the post, but you can see the complete code that builds the main menu GUI and responds to user actions?the MIDlet of the application?in Listing 1. The main difference is that, to retrieve a post, the application sends the user's username and password plus the username of the post's author. The response will be the latest post uploaded by that author. The post downloaded is in a CSV-like form, using the pipe character as a separator, for example:
alessandro|2006-06-28 10:51:23|First Title|First Text
You might also use an XML-like format similar to the one used in my previous article.
In the preceding sample response, alessandro is the username of the post's author followed by the posting date, title, and text. Note that if you intend to allow the pipe character within posts you should use a different separator or some other technique to distinguish your fields.
Figure 7 shows the form displayed by the application when you download a post.
The Server-side Code
Two server-side scripts are responsible for storing (post_sender.php) and retrieving (post_retriever.php) posts. Here's the code to store a post:
The preceding script first retrieves the data sent by the client?username, password, title, and text. Then it checks the validity of the credentials. If they are invalid, the script returns an error message; otherwise it stores the post and sends a success notification back to the client. Similarly, the post_retriever script first checks the credentials and when they're valid, retrieves the latest post sent by the author supplied as a parameter. Here is the code:
You might have noticed that both scripts include the file config.inc.php, which contains the database-related functions.
Limitations and Improvements
Of course, this is not a complete application. Indeed, you might improve it in many ways. One improvement would be to store the retrieved posts locally using RMS. Another is to let users download more than one post at a time using a date range as a selection parameter. The principal limit of the application as shown is that it uses the HTTP GET method, which limits the number of characters you can send in the query string. The POST method would have been a better choice but using it here would have shifted the article's focus away from J2ME/PHP/MySQL interaction. If you plan to enhance this example for production purposes, I'd suggest you start by switching the application to use the POST method.