Porting Android Code to BlackBerry

Article summary

After completing an Android application, we were asked to port it to BlackBerry. The Android code follows the Presenter First design pattern; therefore, we initially assumed that we would be able to re-use all the models. However, porting even the non-UI code was surprisingly difficult: The BlackBerry JVM is based on Java ME (a limited subset of the standard Java SE); therefore,

  • the compiler supports Java language features only up to version 1.3,
  • many useful classes, including java.util.ArrayList, java.util.HashMap, and all of java.lang.reflect are not available, and
  • some classes have fewer methods than their Java SE counterparts. For example, the Object class in the BlackBerry API doesn’t have finalize or clone.

Rather than re-writing all the code (and assuming the resulting maintenance risks), we found techniques that allowed us to share all of our model code with only minimal changes to the Android code base. This article discusses those techniques.

The first step was to set up a familiar and comfortable development environment. This was slightly challenging because BlackBerry provides Windows- and Eclipse-centric tools, whereas we use Macintosh and IntelliJ.

After setting up our development environment, we needed to

  1. Get kSoap Running on BlackBerry
  2. Remove Android-Specific Code From Models
  3. Remove/Re-write Code that Cannot be Made Java 1.3 Compliant
  4. Port Some Java SE classes to BlackBerry
  5. Ignore Unused Java 1.5 Features
  6. Copy the Model Code Into the BlackBerry Project
  7. Prepare .jar File
  8. Write new presenter and views for BlackBerry
  9. Write a Factory class to do the work that Google Guice and roboguice did for Android
  10. Test

Although we present these steps sequentially, we actually stumbled through them iteratively. The steps may appear complicated; however,

  1. It’s only complicated if you write your Android code with no thought at all to BlackBerry. If you keep the possibility of a BlackBerry port in the back of your mind, you can easily avoid the need for the more complex and time-consuming fixes.
  2. It’s worth taking the time to port the code because you can utilize your existing tests, thereby minimizing the risk of introducing new bugs. In addition, sharing the code simplifies maintenance.

We conclude this article with

kSoap

Our application displays data obtained from a SOAP service. We used a version of the kSoap2-android library modified to add processing of attributes.

Fortunately, the kSoap2 core is already written for Java ME so it and its dependencies will compile on BlackBerry. However, the dependencies, (kxml2, xmlpull, and kobjects) are not part of the standard BlackBerry API and must be included with the project. In theory, each of these .jar files can be preverified and loaded on to the BlackBerry; however, we had trouble with this. We found that it was much easier to build a single .jar file containing all kSoap2 code and its dependences than to separately preverify and include each dependency’s .jar file.

We began with a git repository prepared by Mike Woods containing kSoap2 and all its dependencies, then made two changes:

  1. We replaced the kSoap code in Mike’s repository with the kSoap-base module in the kSoap2-android library; and
  2. we added the code from the ksoap2-midp module in the kSoap2-android library. (These classes, org.ksoap2.transport.HttpTransport and org.ksoap2.transport.ServiceConnectionMidp, provide the http connection used by kSoap on BlackBerry.)

We then

  1. compiled the source code,
  2. generated a .jar file,
  3. ran preverify on the .jar file, and
  4. included the .jar file produced by preverify in our app.

We could also have simply included all the source in our project.

BlackBerry Enhanced Network API

The ksoap2-midp module that comes with kSoap2-android does work with BlackBerry, but it will connect through the MDS server only. If you want the app to try different transport types (MDS, cellular, wifi, etc), you must use BlackBerry’s Enhanced Network API. To utilize this feature, we wrote two classes

  • HttpTransportBlackBerry, and
  • ServiceConnectionBlackBerry.

ServiceConnectionBlackBerry provides an implementation of the ServiceConnection interface that uses the Enhanced Network API. (It is identical to ServiceConnectionMidp, except for the few lines that create the HttpConnection object.) HttpTrasportBlackBerry is a subclass of HttpTransport that overrides getServiceConnection so that it returns a ServiceConnectionBlackBerry object. The code is appended.

Remove Android-Specific Code From Models

There were places where some Android-specific code had snuck into the models. In particular

  • some of the models had methods that took android.location.Geocoder objects as parameters, and
  • some of the models wrote error messages to android.Log.

The first case was easily solved by writing a LatitudeLongitudePair class to contain the relevant Geocoder data. Instead of the view pushing the Geocoder object down to the model, it copied the relevant data from Geocoder into the LatitudeLongitude object and passed that down to the model. The corresponding BlackBerry view behaves similarly.

The second case required slightly more work to fix because the Android log methods are static members of the class android.util.Log. We wrote a class FLog (for Flexible Log) that contained the same methods as android.util.Log. We also wrote an abstract Logger class with corresponding instance methods. FLog contains a static Logger variable. The FLog methods call the corresponding Logger methods. The particular implementation of Logger given to FLog determines how the data is logged. The code is appended.

Not only did adding FLog to the Android code remove references to android.util.Log from BlackBerry code, but it improved our ability to test our Android code. Our unit tests run on a regular Java SE platform, not on the Android platform. Consequently, the unit tests would generate a runtime error whenever a model detected an error and attempted to call method in android.Log With our FLog setup, we could provide a Logger implementation that writes to android.util.Log when running the app, and a Logger implementation that writes to System.err when running unit tests, thereby avoiding the runtime exception.

Remove/Re-write Code that Cannot be Made Java 1.3 Compliant

Retrotranslator can convert Java 1.5 bytecode into Java 1.3 compatible bytecode. Retrotranslator allows us to use our Android code without removing Java 1.5 features such as generics and autoboxing. It even provides implementations for methods that were added after Java 1.3. However, it does not implement classes and methods that are absent from Java ME. Thus, we had to remove references to java.util.ArrayList, java.util.HashMap, and the methods String.format and DateFormat.parse from the code we wanted to port to BlackBerry.

To handle the missing methods, we wrote a class PlatformLib with static methods parseDate and formatString. We then had separate versions of PlatformLib for Android and BlackBerry. The Android version of PlatformLib simply called the desired methods. The BlackBerry version actually implements needed functionality. (It handles only those cases needed by the BlackBerry app. It does not provide a general replacement for String.format or SimpleDate.parse.)

Base64

Our Android code used net.iharder.Base64 do do some base 64 decoding. This code provides several helper methods that connect the encoding and decoding to IO streams. Some of these streams are not present on BlackBerry, so we had do remove these unused helper methods.

Guice Providers

Several of our models used the Google Guice Provider class to generate instances of aggregated objects. We could have easily removed the providers by replacing them with calls to new; however, using the provider greatly simplifies testing because it provides a means for injecting mock objects into the classes under test.[1] Therefore, rather than completely removing the providers, we wrote simple, custom providers for about five classes. Using the providers in Android required only changing the type of the Provider instance variables and constructor parameters. Guice handled the rest. (Notice the annotations.)

Fortunately, there were not many classes that use providers to instantiate aggregated objects, and each custom provider was less than 10 lines of code. For future projects, We are confident that we can find a way of automating the generation of providers. It may be even possible to write a general provider that will run on BlackBerry provided the classes being generated have a zero-parameter constructor.

Port Some Java SE classes to BlackBerry

Replacing java.util.ArrayList, java.util.HashMap was a little more challenging. Removing all references to java.util.List and java.util.Map would have required significant changes to the Android code. We attempted to port ArrayList and HashMap to BlackBerry by extracting List.class, ArrayList.class, and other dependent .class files from rt.jar and running them through Retrotranslator. We quickly discovered that the list of dependencies for ArrayList is quite large and includes classes that can not be easily run on BlackBerry. For example, toArray uses java.lang.reflect.Array.

Fortunately, the interfaces in java.util don’t have nearly as many dependencies. We were able to extract java.util.List, java.util.Map, and their dependencies from rt.jar and run them through Retrotranslator and preverify. We then provided our own implementation of List and HashMap. They are simple wrappers around the Java ME classes java.util.Vector and java.util.Hashtable. As with the methods in PlatformLib, we implement and test only those features our app uses. We don’t, for example, implement ArrayList.toArray, ListIterator, or Iterator.remove.

Finally, we add methods makeArrayList and makeHashMap to PlatformLib which return java.util.ArrayList and java.util.HashMap on the Android and our Vector and Hashtable wrappers on the BlackBerry.

For purposes of using List and HashMap, we extracted the following files from rt.jar:

  • java/util/List.class
  • java/util/Collection.class
  • java/util/Iterator.class
  • java/util/ListIterator.class
  • java/util/Map.class
  • java/util/Set.class
  • java/util/Map$Entry.class

Ignore Unused Java 1.5 Features

Retrotranslator can handle annotations; however, many uses of annotations require reflection, which is not available on the BlackBerry platform.

Our Android code used annotations only for the purposes of using the Guice dependency injection framework. Guice uses reflection to access annotation parameters and create the requested objects; therefore, we don’t believe it is possible to port Guice to BlackBerry. However, the presence of the annotations themselves need not cause a problem. Thus, we extracted the following interfaces from Guice:

  • com/google/inject/ImplementedBy.class
  • com/google/inject/Singleton.class
  • com/google/inject/Inject.class
  • com/google/inject/ScopeAnnotation.class

The presence of these classes in the classpath allows Retrotranslator and preverify to accept the code with the annotations; however, take care not to try and access them.

Serializable

Similarly, we extract java/io/Serializable.class from rt.jar to allow classes declared Serializable to be automatically ported.

Rename packages

BlackBerry devices don’t allow users to add code to core packages (including java.*). Consequently, it rejected our code which included translated versions of java.util.List and java.util.Map taken from rt.jar. We solved this problem by using jarjar to move List and Map from java.util to a different package. We discovered this problem at the last minute, and our solution is a bit of a hack. We have not yet developed a cleaner solution that we like. (One idea is to take the open source of java.util.List and java.util.Map and move it to a different package, then use that new package in both the Android and BlackBerry code. However, this may limit our ability to use Lists and Maps with other classes in java.util.)

Copy the Model Code into the BlackBerry Project

Once we had made and tested the necessary modifications to the Android code, we copied each of the desired .java files from our Android project into our BlackBerry project. We suspect we could have simply pointed the BlackBerry project’s compile task at the Android source tree; however, that simply didn’t occur to us at the time.

In our case, simply pointing the compile task at the Android source tree may not have worked because having the Android classes we weren’t porting (i.e., the views and presenters) in the classpath may have caused problems. For similar reasons, writing the Ant task that copied only the desired files was also a little challenging. Next time we’ll plan ahead and either put code to be used on both BlackBerry in a separate package, or in a parallel source tree.

Prepare .jar File

Once we have prepared the Android code, we can transform it into a .jar file that can be used by a BlackBerry app.

  1. Extract the aforementioned .class files from rt.jar and Guice and place them in a directory. (We’ll call it elib.) Be sure to maintain the directory/package structure.
  2. Compile the Android code. We’ll assume the resulting .class files are placed into build. (Make sure build contains only the .class files for the code to be ported.)
  3. Use the Ant task below to run Retrotranslator.
    • Notice that we are running Retrotranslator on both the compiled Android code and the extracted code in elib.
    • We need our preverified kSoap library in the classpath because the Android models depend on that code.
    • “backport” is the process by which Retrotranslator fills in code that isn’t present in Java 1.3. The Ant task below tells Retrotranslator to provide code to implement annotations. That code is contained in retrotranslator-runtime13-1.2.9.jar.
    • The smart parameter makes all backport classes inheritable. We didn’t explicitly use this feature.
    • The verify parameter issues warnings if there are “references to classes, methods, or fields that cannot be found in the classpath.”
  4. Run preverify -d ${pv_output} -classpath net_rim_api.jar:${kSoap}:retrotranslator-runtime13-1.2.9.jar ${retro_build}
    • preverify analyzes all of the classes in the directory ${retro_build}, generates a new, parallel set of .class files and places them in ${pv_output}.
    • All classes must be accounted for. The file net_rim_api.jar contains the core BlackBerry / Java ME classes (Object, String, etc.).
    • Notice that retrotranslator-runtime13-1.2.9.jar appears in the classpath, instead of the input. We are telling preverify that we will provide the code in retrotranslator-runtime13-1.2.9.jar separately. In fact, we never provide this code to the BlackBerry app because it uses reflection and, therefore, can’t be ported. Referencing any of this back-ported code will result in a runtime exception. In our particular case, avoiding the missing backported code simply means that we do not access annotations at runtime.
  5. Create a .jar file containing the contents of ${pv_output}. This .jar file can be imported into a BlackBerry project and used by the “presenter” and “view” classes.

Testing

Setting up unit tests requires some care because the BlackBerry code will not run on a standard JVM. This is not a problem for classes such as String and Vector which exist on both Java SE and BlackBerry / Java ME; but, it does mean that unit tests cannot reference user interface or other BlackBerry-specific classes. The solution is to use a model-view-presenter -based design: Place all BlackBerry-specific (e.g., UI) code in the view classes and write unit tests for the models and presenters only.

Isolating BlackBerry-specific code in views is fairly straightforward, there are just a few places that require care:

Launching new screens

The presenters cannot directly launch a new screen. For example, they cannot contain this code: UiApplication.getUiApplication().pushScreen(screen); because UiApplication will run only on the device (or simulator).

Take care to avoid hidden references to BlackBerry-only classes. We made this mistake early in our BlackBerry development: We wrote a base class for all views and added a method public void launchScreen(Screen screen) with the intention of having the presenters call view.launchScreen(screen). The problem is that net.rim.device.api.ui.Screen is also a BlackBerry-only class, and simply obtaining a reference to a Screen object at run-time in the Java SE environment is challenging.

The solution is to have each view implement a method to launch the desired screen. For example, the LoginView class has a method launchMenuView which the presenter calls, thereby keeping all references to BlackBerry-only classes out of the presenter (and out of the code under test).

Dialog boxes

Although it is possible to launch modal dialog boxes outside the context of a specific BlackBerry screen, net.rim.device.api.ui.component.Dialog runs on BlackBerry only. Thus, presenters cannot directly launch dialog boxes. Instead, add a method to a view class to launch the dialog box, and have the presenter call that method.

Transport and kSoap

The kSoap code that handles the network connection is different for Java SE, JavaME, and BlackBerry. JavaME use org.ksoap2.transport.HttpTransport, BlackBerry uses the aforementioned HttpTransportBlackBerry, and Java SE uses org.ksoap2.transport.HttpTransportSE. All are subclasses of org.ksoap2.transport.Transport, therefore we wrote a provider:

When running the BlackBerry app, we configure our SOAP code to use: org.ksoap2.transport.HttpTransport

When running tests that access the SOAP server, we configure our SOAP code to use a different provider:

The SOAP code then calls makeTransport when establishing a new connection and gets the proper object for the platform on which its running.

What We’ll Do Differently Next Time

Here is what we will do differently the next time we write an Android app with the possibility of a BlackBerry follow-up:

  • Put the potentially re-useable code in a different package (or possibly in the same package but a different source tree) to simplify the process of extracting the code to be ported to BlackBerry.
  • Take more care to keep Android-specific classes (such as Log and Geocoder) out of the models.
  • Avoid using methods that are not available on BlackBerry (such as String.format, printf, and SimpleDate.parse).

Open Questions

  • Can we extract com.google.inject.Provider<T> from Guice and use it in BlackBerry apps (even though we probably can’t use the rest of Guice)?
  • Are there existing and (more importantly) well-tested implementations of List and Map that will run on BlackBerry?
  • Can we load and re-launch a re-compiled app without resetting the simulator?
  • Is there a more efficient method of pushing the .cod file to the simulator and re-launching the app on the simulator?

Notes

1 Suppose class Student has a method that must generate a new instance of the class Excuse. Using a provider to generate the Excuse simplifies testing as follows: When creating the Student object in the unit test, pass a mock Excuse provider to the Student constructor. Then, configure the mock Excuse provider to return a mock Excuse object when its get method is called.


Configure kSoap to use BlackBerry’s Enhanced Network API



FLog, Our Flexible Log class

Conversation
  • Jesse Wilson says:

    Its probably against the license to redistribute classes from rt.jar. But you can grab the same classes as source from Android.

  • Jan says:

    Would it be at all possible to make the previrified ksoap library available for download? Would help allot…

  • Redistributing the classes from rt.jar is mostly a moot point now, because the BlackBerry won’t accept them anyway. Thanks for the pointer to the Android source.

    I will look into making the preverified kSoap library available. (Let me double check the licensing.)

  • Nice article – great tips for the porting issues. We are constantly trying to figure out a better workflow to produce apps for the various platforms with as little work as possible. It seems I have been walking a similar path to what you write about – but you have laid it out very nicely.
    Thanks

  • Noureen says:

    hi!
    i am trying to set the volume blackberry through seek bar in android but the blackberry not geting any changes. blackberry only allows to get the volume but not to set the volume in balcberry palybook.
    here is my code:

    this.setVolumeControlStream(AudioManager.STREAM_MUSIC);

    sound.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

    public void onStopTrackingTouch(SeekBar seekBar) {
    // TODO Auto-generated method stub

    }

    public void onStartTrackingTouch(SeekBar seekBar) {
    // TODO Auto-generated method stub

    }

    public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {
    // TODO Auto-generated method stub

    audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0 );

    }

    });

  • Comments are closed.