Luaj lcdui

Luaj is a library bringing Lua to Java 2 ME platform. It does not have the bindings to javax.microedition.lcdui package. I wanted to see how easy it is to add a few classes from this package in order for them to be available to Lua programs. You can grab the source from here.

Main MIDlet

Here’s the MIDlet code:

package com.icyrock.j2me.luaj.example;

import java.io.IOException;
import java.io.InputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

import org.luaj.vm2.LuaValue;
import org.luaj.vm2.lib.OneArgFunction;
import org.luaj.vm2.lib.jme.JmePlatform;

import com.icyrock.j2me.luaj.LcduiLib;
import com.icyrock.j2me.luaj.Midlet;

public class LuajMidlet extends MIDlet {
  protected void startApp() throws MIDletStateChangeException {
    System.out.println("< startApp");

    final String script = "phi";

    LuaValue _G = JmePlatform.standardGlobals();
    _G.load(new LcduiLib());

    Midlet midlet = (Midlet) _G.get("lcdui").get("midlet").get("current");
    midlet.setJ2meMidlet(this);

    LuaValue luaLoadFunc = new OneArgFunction() {
      boolean loaded = false;

      public LuaValue call(LuaValue arg) {
        if (!loaded) {
          String luaScript = readFile("file:///SDCard/icyrock/lua/" + script + ".lua");
          loaded = true;
          return valueOf(luaScript);
        }
        return NIL;
      }
    };
    _G.get("load").call(luaLoadFunc).call();

    System.out.println("< startApp");
  }

  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
  }

  protected void pauseApp() {
  }

  private String readFile(String fileName) {
    String ret = "";

    FileConnection fc = null;
    try {
      fc = (FileConnection) Connector.open(fileName);
      InputStream is = fc.openInputStream();

      StringBuffer sb = new StringBuffer();

      int cap = 2000;
      byte[] bytes = new byte[cap];

      while (true) {
        int available = is.available();
        if (available == 0) {
          break;
        }

        int len = available > cap ? cap : available;
        is.read(bytes, 0, len);

        sb.append(new String(bytes, 0, len, "UTF-8"));
      }

      ret = sb.toString();
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (fc != null) {
        try {
          fc.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    return ret;
  }
}

It has a readFile method, which is nothing else but a method to read files. Java 2 ME is pretty limited, so there are no e.g. BufferedReader or classes such as that to help with this task, so I added this quick method to read the file in whole.

The main code is in startApp method, the breakdown of which follows:

  protected void startApp() throws MIDletStateChangeException {
    System.out.println("< startApp");

    final String script = "phi";

When used in an emulator, such as MicroEmulator, just print the string to know this is starting to be loaded. Also, define the Lua script to be run.

    LuaValue _G = JmePlatform.standardGlobals();
    _G.load(new LcduiLib());

    Midlet midlet = (Midlet) _G.get("lcdui").get("midlet").get("current");
    midlet.setJ2meMidlet(this);

Define the _G, which is the global Luaj context. Add our LcduiLib, so it’s accessible to Lua scripts we write. Initialize a variable lcdui.midlet.current that will, when accessed from within Lua script, point to the current MIDlet instance.

    LuaValue luaLoadFunc = new OneArgFunction() {
      boolean loaded = false;

      public LuaValue call(LuaValue arg) {
        if (!loaded) {
          String luaScript = readFile("file:///SDCard/icyrock/lua/" + script + ".lua");
          loaded = true;
          return valueOf(luaScript);
        }
        return NIL;
      }
    };

Define the loader function. This function will be called to load all parts of Lua script, which will then be concatenated and run. The script is loaded from:

"file:///SDCard/icyrock/lua/" + script + ".lua"

so in this case that would be:

file:///SDCard/icyrock/lua/phi.lua

containing a sample Lua script we’ll see later. If you use MicroEmulator under Linux, you should put this file in:

~/.microemulator/filesystem/SDCard/icyrock/lua/phi.lua 

The last step:

    _G.get("load").call(luaLoadFunc).call();

    System.out.println("< startApp");
  }

is to load the Lua script and run it.

Sample Lua script

You can find this one in com/icyrock/j2me/luaj/example/lua/phi.lua:

print('> phi')

lcdui = require('lcdui')
display = lcdui.display.get_display(lcdui.midlet.current)

form = lcdui.form.create('phi')

form.set_command_listener(
  function(command, displayable)
    if command.get_label() == 'Exit' then
      lcdui.midlet.current.notify_destroyed()
    end
  end
)

cmd_exit = lcdui.command.create('Exit', 7, 1)
form.add_command(cmd_exit)
cmd_exit = lcdui.command.create('Exit', 7, 2)
form.add_command(cmd_exit)

txt_hi = lcdui.string_item.create('Message', 'Hi from Luaj!')
form.append(txt_hi)

display.set_current(form)

print('< phi')

The breakdown is:

print('> phi')

lcdui = require('lcdui')
display = lcdui.display.get_display(lcdui.midlet.current)

Get the display. This is the standard J2ME lcdui stuff.

form = lcdui.form.create('phi')

form.set_command_listener(
  function(command, displayable)
    if command.get_label() == 'Exit' then
      lcdui.midlet.current.notify_destroyed()
    end
  end
)

Create a from and put a command listener. The listener is just going to destroy the form when a command with label "Exit" is chosen.

cmd_exit = lcdui.command.create('Exit', 7, 1)
form.add_command(cmd_exit)
cmd_exit = lcdui.command.create('Exit', 7, 2)
form.add_command(cmd_exit)

Create two commands, both of which are used for exiting and add them to the form. In MicroEmulator, this allows for both shown commands to be used to exit the script.

txt_hi = lcdui.string_item.create('Message', 'Hi from Luaj!')
form.append(txt_hi)

display.set_current(form)

print('< phi')

Add a sample text message with label "Message" and the string "Hi from Luaj!" as the main text. Finally, set the display to be visible (again standard J2ME).

Note two things:

  • Lua function print is going to print to MicroEmulator's console
  • You can exit from MIDlet in the emulator, change the Lua script, run the MIDlet again and the changes will be applied

Developing Bluetooth-enabled apps with Java

A good starting explanation about network programming in general comes from this page. Look at the whole book – it’s called “An Introduction to Bluetooth Programming” and I find it a very good read.

Emulation

Before we start, I’d like to mention that, for development, it’s much easier to develop using emulation of bluetooth / mobile phone. For that, I’ve used BlueCove and MicroEmulator, respectively. The glue between them is the BlueCove JSR-82 Emulator module.

Scenario

The situation we’ll examine is simple. We have two devices – in this case a PC and a phone – communicating with each other. The example would be a simple searcher for Stack Exchange sites. The user on the mobile phone would type a word, that gets sent to the server on the PC, it searches using api.stackexchange.com and returns the results to the phone, which displays them.

Following are the steps needed for the above to happen.

Discovery

To communicate, a bluetooth app uses the SDP (Service Discovery Protocol) to find the other devices and services they provide. In this case, as PC acts as the server, the phone would initiate the discovery, i.e. be a client. The basic idea is presented in BlueCove’s javadoc. I’ve made a helper class called BluetoothUtils:

package com.icyrock.bluetooth;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.bluetooth.DataElement;
import javax.bluetooth.DeviceClass;
import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.DiscoveryListener;
import javax.bluetooth.LocalDevice;
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.bluetooth.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class BluetoothUtils {
  private static final int btAttrServiceName = 0x0100;
  private final static Logger log = LoggerFactory.getLogger(BluetoothUtils.class);

  public List<RemoteDevice> discoverDevices() {
    final Object sync = new Object();
    final List<RemoteDevice> devs = new ArrayList<RemoteDevice>();

    DiscoveryListener listener = new DiscoveryListener() {
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) {
        try {
          log.info("Found device: address: {}, name: {}",
            dev.getBluetoothAddress(),
            dev.getFriendlyName(false));
          devs.add(dev);
        } catch (IOException e) {
          log.error(e.toString(), e);
        }
      }

      public void inquiryCompleted(int discType) {
        log.info("Device inquiry completed");
        synchronized (sync) {
          sync.notifyAll();
        }
      }

      public void serviceSearchCompleted(int transId, int respCode) {
      }

      public void servicesDiscovered(int transId, ServiceRecord[] servRecord) {
      }
    };

    synchronized (sync) {
      boolean started;
      try {
        started = LocalDevice.getLocalDevice().getDiscoveryAgent()
          .startInquiry(DiscoveryAgent.GIAC, listener);
        if (started) {
          log.info("Device inquiry started");
          sync.wait();
          log.info("Devices count: {}", devs.size());
        }
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    return devs;
  }

  public List<String> searchForServices(List<RemoteDevice> devices, String uuidStr) {
    final Object sync = new Object();
    final List<String> urls = new ArrayList<String>();

    DiscoveryListener listener = new DiscoveryListener() {
      public void deviceDiscovered(RemoteDevice dev, DeviceClass devClass) {
      }

      public void inquiryCompleted(int discType) {
      }

      public void servicesDiscovered(int transId, ServiceRecord[] servRecord) {
        for (int i = 0; i < servRecord.length; i++) {
          String url = servRecord[i].getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
          if (url != null) {
            urls.add(url);
            DataElement name = servRecord[i].getAttributeValue(btAttrServiceName);
            log.info("Service found: url: {}, name: {}", url, name);
          }
        }
      }

      public void serviceSearchCompleted(int transId, int respCode) {
        log.info("Service search completed");
        synchronized (sync) {
          sync.notifyAll();
        }
      }

    };

    UUID[] uuidArr = new UUID[] { new UUID(uuidStr, false) };
    int[] attrIds = new int[] { btAttrServiceName };

    for (RemoteDevice device : devices) {
      synchronized (sync) {
        try {
          log.info("Searching for service: {} on device: {} / {}",
            new Object[] { uuidStr, device.getBluetoothAddress(), device.getFriendlyName(false) });
          LocalDevice.getLocalDevice().getDiscoveryAgent()
            .searchServices(attrIds, uuidArr, device, listener);
          sync.wait();
        } catch (Exception e) {
          log.error(e.toString(), e);
        }
      }
    }

    return urls;
  }
}

This one has two methods:

  • public List<RemoteDevice> discoverDevices() – Searches for the devices and returns a list of devices that have been found
  • public List<String> searchForServices(List<RemoteDevice> devices, String uuidStr) – Searches for the service given by uuidStr on the devices in the devices list. It returns the URLs that can be used to access these services

These methods are going to be used in client and server later.

Client

The client is fairly simple:

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import javax.bluetooth.RemoteDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;
import com.icyrock.spring.SpringUtils;

@Component
public class StackExchangeBluetoothRfcommClient {
  private final static Logger log = LoggerFactory.getLogger(StackExchangeBluetoothRfcommClient.class);
  @Autowired
  private BluetoothUtils bluetoothUtils;
  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";

  public void run() {
    try {
      startClient();
    } catch (Throwable t) {
      log.error(t.toString(), t);
    }
  }

  private void startClient() throws Exception {
    List<RemoteDevice> devices = bluetoothUtils.discoverDevices();
    List<String> urls = bluetoothUtils.searchForServices(devices, uuid);

    if (urls.size() > 0) {
      String keywords = "bluetooth";

      StreamConnection streamConn = (StreamConnection) Connector.open(urls.get(0));

      OutputStream os = streamConn.openOutputStream();
      IOUtils.writeLines(Lists.newArrayList(keywords), null, os);
      os.close();

      InputStream is = streamConn.openInputStream();
      String result = IOUtils.toString(is);
      is.close();

      log.info("Result: {}", result);

      streamConn.close();

      log.info("Client done");
    }
  }

  public static void main(String[] args) {
    System.setProperty("bluecove.stack", "emulator");
    StackExchangeBluetoothRfcommClient sebc = SpringUtils.getDefaultContext()
      .getBean(StackExchangeBluetoothRfcommClient.class);
    sebc.run();
  }
}

Aside from the boilerplate code needed, there are two important notes above. First, in main method, there’s this line:

    System.setProperty("bluecove.stack", "emulator");

This one is used when testing this with BlueCove Bluetooth emulation, to signal BlueCove to use the emulator instead of the real bluetooth stack. Read more about this here.

Second, note this line:

  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";

This one defines the UUID of the service that we are going to look for. In Bluetooth, all services are represented by UUID. To generate these in Linux, you can do something like:

$ uuidgen|tr -d '-'
db56eca4204d4f478063dacab16e805f

The other things are all in startClient method. The method does this:

  • Searches for all devices
  • Searches for the given service (identified by the uuid) on all found devices
  • If the search result yields any URLs, pick the first one (the other option would be to present the user with the choice of which URL to use)
  • Open a StreamConnection – this is basically a RFCOMM channel
  • Write the search keywords to the output stream
  • Read the response from the server and log it

Note that I’m using a few helper libraries here:

Here’s a Gradle config for this project:

apply plugin: 'java'

repositories {
  mavenCentral()
}

dependencies {
  compile 'net.sf.bluecove:bluecove:2.1.0'
  runtime 'net.sf.bluecove:bluecove-gpl:2.1.0'
  runtime 'net.sf.bluecove:bluecove-emu:2.1.0'
  
  compile 'ch.qos.logback:logback-classic:1.0.7'
  compile 'commons-io:commons-io:2.4'
  compile 'org.apache.httpcomponents:httpclient:4.2.1'
  compile 'org.apache.httpcomponents:fluent-hc:4.2.1'
  compile 'com.fasterxml.jackson.core:jackson-databind:2.1.0'
 
  compile 'org.springframework:spring-context:3.1.2.RELEASE'
  runtime 'asm:asm:3.3.1'
  runtime 'cglib:cglib-nodep:2.2.2'
  runtime 'org.slf4j:jcl-over-slf4j:1.6.6'
  
  compile 'com.google.guava:guava:13.0.1'
}

StreamUtils

One problem that I encountered is that you cannot use BufferedReader to read from InputStream provided by the bluetooth connection. The issue is that readLine will block until the whole line is read, but the way it works is it reads chunks of data to be efficient and thus doesn’t know when the line ends. In order to circumvent this, I’m using a simple char-by-char reading, which is hacky, inefficient and might not work properly sometimes, but solves this problem and works well for this example.

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.google.common.base.Joiner;

@Component
public class StreamUtils {
  private final static Logger log = LoggerFactory.getLogger(StreamUtils.class);

  private String readLine(InputStream inputStream) {
    StringBuilder sb = new StringBuilder();
    int sleeps = 0;
    boolean haveNewLine = false;

    while (true) {
      int byteRead;
      try {
        if (inputStream.available() == 0) {
          if (sleeps < 3) {
            sleeps++;
            Thread.sleep(1000);
            continue;
          } else {
            break;
          }
        }

        byteRead = inputStream.read();
      } catch (Exception e) {
        throw new RuntimeException(e);
      }

      if (byteRead == -1) {
        break;
      }

      char ch = (char) byteRead;
      if (ch == '\n') {
        haveNewLine = true;
        break;
      }
      sb.append(ch);
    }
    return haveNewLine ? sb.toString() : null;
  }

  public List<String> readLines(InputStream inputStream) {
    List<String> lines = new ArrayList<>();
    while (true) {
      String line = readLine(inputStream);
      if (line == null) {
        break;
      }
      lines.add(line);
    }
    return lines;
  }

  public String readLinesAsStr(InputStream inputStream) {
    return Joiner.on("\n").join(readLines(inputStream));
  }
}

Server

Server is pretty simple, too:

package com.icyrock.bluetooth;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;

import javax.bluetooth.DiscoveryAgent;
import javax.bluetooth.LocalDevice;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.fluent.Request;
import org.apache.http.client.utils.URIBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.icyrock.spring.SpringUtils;

@Component
public class StackExchangeBluetoothRfcommServer {
  private final static Logger log = LoggerFactory.getLogger(StackExchangeBluetoothRfcommServer.class);
  private final static String uuid = "88e08d4875344ff88f18ec4cc70ee702";
  @Autowired
  private StreamUtils streamUtils;

  public void run() {
    try {
      startServer();
    } catch (Throwable t) {
      log.error(t.toString(), t);
    }
  }

  private void startServer() throws Exception {
    LocalDevice.getLocalDevice().setDiscoverable(DiscoveryAgent.GIAC);

    StreamConnectionNotifier serverConnection = (StreamConnectionNotifier) Connector.open(
      "btspp://localhost:" + uuid + ";name=StackExchangeBluetoothServer");

    while (true) {
      log.info("Waiting for connection");
      StreamConnection streamConn = serverConnection.acceptAndOpen();

      log.info("Request received");

      InputStream is = streamConn.openInputStream();
      String content = streamUtils.readLinesAsStr(is);
      is.close();

      List<String> resp;
      if (content == null || content.equals("")) {
        log.info("Empty keywords received from the client.");
        resp = Lists.newArrayList("Please provide keywords to search for");
      } else {
        resp = queryStackExchange(content);
      }

      OutputStream os = streamConn.openOutputStream();
      IOUtils.writeLines(resp, null, os);
      os.close();

      streamConn.close();
    }
  }

  @SuppressWarnings("unchecked")
  private List<String> queryStackExchange(String keywords) throws Exception {
    // Uncomment if you want to test without actually making the calls to StackExchange
//    if(true) {
//      return Lists.newArrayList("title1", "title2", "title3");
//    }
    
    URI uri = new URIBuilder()
      .setScheme("https")
      .setHost("api.stackexchange.com")
      .setPath("/2.1/search")
      .setParameter("order", "desc")
      .setParameter("intitle", keywords)
      .setParameter("site", "stackoverflow")
      .setParameter("pagesize", "5")
      .build();

    HttpResponse httpResp = Request.Get(uri)
      .connectTimeout(5000)
      .socketTimeout(5000)
      .execute().returnResponse();

    List<String> ret = new ArrayList<String>();
    if (httpResp.getStatusLine().getStatusCode() == 200) {
      HttpEntity httpEnt = httpResp.getEntity();

      InputStream is = httpEnt.getContent();
      Header contentEncoding = httpResp.getFirstHeader("Content-Encoding");
      if (contentEncoding != null && "gzip".equals(contentEncoding.getValue())) {
        is = new GZIPInputStream(is);
      }
      String response = IOUtils.toString(is);
      is.close();

      ObjectMapper om = new ObjectMapper();
      Map<String, Object> map = om.readValue(response, Map.class);
      List<Map<String, Object>> items = (List<Map<String, Object>>) map.get("items");

      for (Map<String, Object> item : items) {
        ret.add((String) item.get("title"));
      }
    }

    log.info("Result: {}", Joiner.on(", ").join(ret));
    return ret;
  }

  public static void main(String[] args) {
    System.setProperty("bluecove.stack", "emulator");
    StackExchangeBluetoothRfcommServer sebs = SpringUtils.getDefaultContext()
      .getBean(StackExchangeBluetoothRfcommServer.class);
    sebs.run();
  }
}

In startServer method, it listens for the connection. When one is established, it reads the keywords from the client. If nothing was read, it will reply with a help message. If keywords were read, it will query the StackExchange. The query is separated into queryStackExchange method and uses Apache HttpClient fluid interface – seems pretty nice to me. If the response was received, it will un-GZIP it if needed and then use Jakson JSON parser to actually get the titles from the response.


Sample J2ME app using Eclipse / STS

I’m using Spring Tools Suite that’s based on Eclipse, which can be downloaded from here, but the process below should be the same in general if you use regular Eclipse. You’ll need STS not based on Eclipse Juno (see this for more info), so download STS 2.9.2, which is based on Eclipse 3.7.2.

For testing, I’ve been using MicroEmulator.

Configuring Eclipse

After getting the base Eclipse / STS distribution, install Mobile Tools for Java (MTJ) from here. There’s a good tutorial on eclipse.org itself on how to make the basic MIDlet and run it in the emulator.

Sample J2ME HTTP fetcher MIDlet

MIDlets are Java classes used as entry points to your application. You have three abstract methods to override:

  • destroyApp – called when the MIDlet is about to be destroyed
  • pauseApp – called when MIDlet is to be paused, so other MIDlet can run instead
  • startApp – called when MIDlet is to be activated (either for the first time or after being paused)

We’ll make a sample HTTP fetcher. I’ll go straight to the code. First, a helper class:

package com.icyrock.j2me.prb;

public interface IcyrockRunnable {
  public void run() throws Exception;
}

This one is the same as the regular java.lang.Runnable with the exception that it can throw Exceptions, just for convenience.

Main class

I’ll dissect the main class now – the whole class is pasted after this if you just want to copy & paste:

package com.icyrock.j2me.prb;

import java.io.InputStream;
import java.util.Hashtable;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class HttpFetchPrb extends MIDlet {
  private Hashtable commands = new Hashtable();
  private Form form;
  private TextField urlTxt;
  private TextField responseTxt;

After some imports, there are 4 fields:

  • commands field is going to store command-to-IcyrockRunnable mapping. Easy for mapping commands
  • form is our top-level form, holding the other GUI controls
  • urlTxt and responseTxt are two GUI controls put on the form that we’ll use to store the URL to fetch from and the response itself (including the eventual errors)
  public HttpFetchPrb() {
    form = new Form(this.getClass().getName());
    form.setCommandListener(new CommandListener() {
      public void commandAction(Command cmd, Displayable arg1) {
        IcyrockRunnable toRun = (IcyrockRunnable) commands.get(new Integer(cmd.getCommandType()));
        if (toRun != null) {
          try {
            toRun.run();
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    });

This is the start of the constructor. We create the form and then initialize the command listener. Command listener is a top-level listener for all commands put on this form. They are differentiated by the command type, which we then map through our commands field and run the required IcyrockRunnable.

    urlTxt = new TextField("URL:", "http://192.168.122.1/", 256, TextField.ANY);
    form.append(urlTxt);

    final int maxLen = 1024;
    responseTxt = new TextField("Response", "", maxLen, TextField.ANY);
    form.append(responseTxt);

We now create the text field holding the actual URL and the response text field. The default URL is put there – in my case, I put 192.168.122.1, but to test you should put the real IP of the machine you are running the emulator on. MicroEmulator has the option to allow MIDlets to access network – this should be enabled by default, but if not, the option is in Options menu / MIDlet Network access.

    int priority = 1;
    addCommand(new Command("Fetch", Command.OK, priority++), new IcyrockRunnable() {
      public void run() throws Exception {

This is how a command is created. You give it the name (“Fetch”) and the type (Command.OK, an int). addCommand method is just a helper method that you’ll see at the end.

        HttpConnection con = (HttpConnection) Connector.open(urlTxt.getString());
        try {
          int respCode = con.getResponseCode();

          String responseStr = "";
          int responseLen = (int) con.getLength();

          int len = responseLen > maxLen ? maxLen : responseLen;
          byte[] respData = new byte[len];

          InputStream ins = con.openInputStream();
          ins.read(respData);
          ins.close();

          responseStr = new String(respData);

          responseTxt.setString("Response code: " + respCode + "\n" + responseStr);
        } catch (Exception e) {
          responseTxt.setString("Error: " + e.getMessage());
        } finally {
          con.close();
        }
      }
    });

This is the gist of the HTTP fetcher. In steps it:

  • Creates the HttpConnection to the given URL
  • Gets the response code
  • Creates the response buffer to hold the response itself (and limits to maxLen because that’s the length of our text field)
  • Opens and reads the input stream
  • Converts that to string
  • Puts the response code and the response itself into the responseTxt text field

There’s a guard for exceptions that prints the exception itself into the response text field. Not sure if that’s the general stance, but in MicroEmulator that I used for testing, fetching the URL that doesn’t exist (i.e. when HTTP response is 404) throws an exception instead of giving a 404 response code.

    addCommand(new Command("Exit", Command.EXIT, priority++), new IcyrockRunnable() {
      public void run() throws Exception {
        destroyApp(false);
        notifyDestroyed();
      }
    });
  }

This command is an exit command. When deliberately exiting, you should destroy the resources and then call notifyDestroyed to notify the container that it wants to get destroyed.

  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
  }

  protected void pauseApp() {
  }

  protected void startApp() throws MIDletStateChangeException {
    Display display = Display.getDisplay(this);
    display.setCurrent(form);
  }

Two required abstract method implementations – we don’t have anything to destroy, nor want to handle app pausing. startApp is important – here we take the display and slap our form on it, so it’s visible.

  private void addCommand(Command command, IcyrockRunnable icyrockRunnable) {
    commands.put(new Integer(command.getCommandType()), icyrockRunnable);
    form.addCommand(command);
  }
}

This is a helper method used above – it just records the command handler and puts it on the form.

Complete main class

Here’s the complete main class:

package com.icyrock.j2me.prb;

import java.io.InputStream;
import java.util.Hashtable;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class HttpFetchPrb extends MIDlet {
  private Hashtable commands = new Hashtable();
  private Form form;
  private TextField urlTxt;
  private TextField responseTxt;

  public HttpFetchPrb() {
    form = new Form(this.getClass().getName());
    form.setCommandListener(new CommandListener() {
      public void commandAction(Command cmd, Displayable arg1) {
        IcyrockRunnable toRun = (IcyrockRunnable) commands.get(new Integer(cmd.getCommandType()));
        if (toRun != null) {
          try {
            toRun.run();
          } catch (Exception e) {
            e.printStackTrace();
          }
        }
      }
    });

    urlTxt = new TextField("URL:", "http://192.168.122.1/", 256, TextField.ANY);
    form.append(urlTxt);

    final int maxLen = 1024;
    responseTxt = new TextField("Response", "", maxLen, TextField.ANY);
    form.append(responseTxt);

    int priority = 1;
    addCommand(new Command("Fetch", Command.OK, priority++), new IcyrockRunnable() {
      public void run() throws Exception {
        HttpConnection con = (HttpConnection) Connector.open(urlTxt.getString());
        try {
          int respCode = con.getResponseCode();

          String responseStr = "";
          int responseLen = (int) con.getLength();

          int len = responseLen > maxLen ? maxLen : responseLen;
          byte[] respData = new byte[len];

          InputStream ins = con.openInputStream();
          ins.read(respData);
          ins.close();

          responseStr = new String(respData);

          responseTxt.setString("Response code: " + respCode + "\n" + responseStr);
        } catch (Exception e) {
          responseTxt.setString("Error: " + e.getMessage());
        } finally {
          con.close();
        }
      }
    });

    addCommand(new Command("Exit", Command.EXIT, priority++), new IcyrockRunnable() {
      public void run() throws Exception {
        destroyApp(false);
        notifyDestroyed();
      }
    });
  }

  protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
  }

  protected void pauseApp() {
  }

  protected void startApp() throws MIDletStateChangeException {
    Display display = Display.getDisplay(this);
    display.setCurrent(form);
  }

  private void addCommand(Command command, IcyrockRunnable icyrockRunnable) {
    commands.put(new Integer(command.getCommandType()), icyrockRunnable);
    form.addCommand(command);
  }
}

Result

Here’s how it looks in MicroEmulator: