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 ofjava.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 havefinalize
orclone
.
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
- Get
kSoap
Running on BlackBerry - Remove Android-Specific Code From Models
- Remove/Re-write Code that Cannot be Made Java 1.3 Compliant
- Port Some Java SE classes to BlackBerry
- Ignore Unused Java 1.5 Features
- Copy the Model Code Into the BlackBerry Project
- Prepare
.jar
File - Write new presenter and views for BlackBerry
- Write a
Factory
class to do the work thatGoogle Guice
androboguice
did for Android - Test
Although we present these steps sequentially, we actually stumbled through them iteratively. The steps may appear complicated; however,
- 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.
- 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:
- We replaced the
kSoap
code in Mike’s repository with thekSoap-base
module in the kSoap2-android library; and - we added the code from the
ksoap2-midp
module in thekSoap2-android
library. (These classes,org.ksoap2.transport.HttpTransport
andorg.ksoap2.transport.ServiceConnectionMidp
, provide the http connection used bykSoap
on BlackBerry.)
We then
- compiled the source code,
- generated a
.jar
file, - ran
preverify
on the.jar
file, and - included the
.jar
file produced bypreverify
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
, andServiceConnectionBlackBerry
.
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.
- Extract the aforementioned
.class
files fromrt.jar
andGuice
and place them in a directory. (We’ll call itelib
.) Be sure to maintain the directory/package structure. - Compile the Android code. We’ll assume the resulting
.class
files are placed intobuild
. (Make surebuild
contains only the.class
files for the code to be ported.) - 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.”
- Notice that we are running Retrotranslator on both the compiled Android code and the extracted code in
- 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 tellingpreverify
that we will provide the code inretrotranslator-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.
- 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
andGeocoder
) out of the models. - Avoid using methods that are not available on BlackBerry (such as
String.format
,printf
, andSimpleDate.parse
).
Open Questions
- Can we extract
com.google.inject.Provider<T>
fromGuice
and use it in BlackBerry apps (even though we probably can’t use the rest ofGuice
)? - Are there existing and (more importantly) well-tested implementations of
List
andMap
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
Its probably against the license to redistribute classes from rt.jar. But you can grab the same classes as source from Android.
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
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 );
}
});