Java on iOS and Windows Phone using GWT

We developed a prototype for a company who wanted to go mobile on iOS and Windows Phone.
The challenge was to avoid rewrite java code in Objective-C and C# as much as possible.
Also running in a native app for a better the user experience.

But there is no JVM available on iPhone, iPad nor Windows Phone. So how to execute Java code?

This tutorial only demonstrate the principle on iOS.
With the exact same code I was able to run it on Windows Phone.

Compiling Java code into JavaScript running in a UIWebView

From Java to iOS

From Java to iOS

The idea is to package our Java in a library.
Then compiling it into a JavaScript using Google Web Toolkit (GWT).

By embedding this generated JavaScript into the iOS app, we can create a web page in memory who runs it.

Objective-C can now call “Java code” throw this UIWebView using JavaScript calls.
As a matter of fact we don’t really call Java code but JavaScript compiled from our Java code.

I – From Java to JavaScript with GWT


First you will need to install GWT for Eclipse.

Now you can create a new GWT project from Eclipse. We use Maven to create the GWT project.

mvn archetype:generate \
   -DarchetypeGroupId=org.codehaus.mojo \
   -DarchetypeArtifactId=gwt-maven-plugin \
   -DarchetypeVersion=2.5.1



Configuration example:
groupId: com.doduck
artifactId: java2javascript
version: 1.0-SNAPSHOT
package: com.doduck
module: java2javascript
Create and import the eclipse project:

mvn eclipse:clean eclipse:eclipse


gwt project structure

gwt project structure




- “client” package: Java source code from this package will be compile into JavaScript
- java2javascript.gwt.xml: configuration of you GWT project

To compile the Java code into JavaScript, move your java classes into the “client” package.
You can also add project dependancy and change this package name but it’s out of the scope of this tutorial.


Let’s make this compile javascript callable from native JavaScript using gwt-exporter
Add in your Maven pom.xml file the dependancy:

[...]
<dependencies>
   [...]
   <dependency>
      <groupId>org.timepedia.exporter</groupId>
      <artifactId>gwtexporter</artifactId>
      <version>2.4.0</version>
   </dependency>
</dependencies>

and update you Eclipse project with:

mvn eclipse:clean eclipse:eclipse


Edit .gwt.xml and add:

<module>
        [...]
	<set-property name="user.agent" value="safari" />
	<add-linker name="sso" />
	<set-property name="export" value="yes" />
</module>



Be aware user.agent=safari will compile into JavaScript for Safari browser only. Change the user.agent to “IE9″ for Windows Phone. The linker “sso” will force to compile into a single JavaScript file

To make the Java code “callable” some gwt-exporter annotation are needed.

For the sake of this tutorial, let’s pretend take this exemple:

@Export
@ExportPackage("jsc")
public class BusinessLogic implements Exportable{

	public int addNumber(int a, int b){
		return a+b;
	}

	public HelloUser buildHelloUser(String name){
		User user = new User();
		user.setName(name);
		user.setAdmin(false);
		return new HelloUser(new Random(), user, listGretter());
	}

	@NoExport
	public List<String> listGretter(){
		List<String> someNames = new ArrayList<String>();
		someNames.add("doduck");
		someNames.add("martin");
		someNames.add("javascript");
		someNames.add("GWT");
		return someNames;
	}
}
@Export
@ExportPackage("jsc")
public class HelloUser implements Exportable{

	private Random random;
	private User user;
	private List<String> fromList;

	public HelloUser(Random random, User user, List<String> fromList) {
		this.user = user;
		this.random = random;
		this.fromList = fromList;
	}

	public String sayHi(){
		String from = fromList.get(random.nextInt(3));
		return "Hello "+ user.getName() + " from "+from;
	}
}
public class User {

	public String name;
	public boolean isAdmin;

	// [getter / setter ]
}



@Export annotation and implementing “Exportable” make the class exportable to JavaScript. @ExportPackage(“jsc”) will give a package name.

Not every GWT object are available outside GWT. It support string[] but not List<String> for example. That why the function “public List<String> listGretter()” is marked as @NoExport.
To manipulate a List<String> you need to encapsulate it into an exportable class.

HelloUser can be directly return by buildHelloUser() and manipulated in JavaScript as it implement “Exportable” and use @Export.

From the documentation gwt-exporter support:
- primitive types like double but not Double.
- Although gwt JSNI does not support long, gwtexporter is able to handle it.
- String
- java.util.Date
- Classes extending JavaScriptObject
- Classes or interfaces implementing Exportable
- Arrays of the above types, but not lists nor collections

And interesting point, you can send callback function.
Also remember sending String mean full object using JSON…

Modify Java2javascript.java to enable gwt-exporter:

public class Java2javascript implements EntryPoint {
	public void onModuleLoad() {
		ExporterUtil.exportAll();
		onLoad();
	}

	private native void onLoad() /*-{
	  if ($wnd.myInit) $wnd.myInit();
	}-*/;
}

The entry point will invoke gwt-exporter when the GWT module is fully loaded in the DOM.
Then it will call function onLoad() if the function exist in native JavaScript. At this point you know you can start using the javascript library.

Compile in javascript using Maven:

mvn clean install

The compile JavaScript file will be in /target/java2javascript-1.0-SNAPSHOT/java2javascript/java2javascript.nocache.js
It’s highly optimised and minify javascript, that what make GWT so quick.

II – Testing in Safari browser

Copy java2javascript.nocache.js into a directory and create an HTML page to test the JavaScript library.

<html>
  <head>
     <script type="text/javascript" src="java2javascript.nocache.js"></script>
     <script>
        function testGWT(){
          var bl = new jsc.BusinessLogic();
          var add = bl.addNumber(1, 2);
          alert("1 + 2 ="+add);

          var helloUser = bl.buildHelloUser("Sochipan");
          alert(helloUser.sayHi());
        }

        function myInit() {
          alert("GWT ready to go");
        }
    </script>
  </head>
  <body>
    <button onclick="testGWT();" >test GWT library</button>
  </body>
</html>



Compiling using user.agent=safari make the library Safari Browser compatible only.

By luck this example work on Chrome and FireFox but it will not for complex project.

III – Running Java GWT in UIWebView

Drag and drop java2javascript.nocache.js into your XCode project.
The javascript source file will added in the Compile Source section. It’s not where it belong!


import in Copy Bundle resource

import in Copy Bundle resource






To embed the javascript library added it in the “Copy Bundle Resources” section.

The final step is to create a wrapping class able the jump from the Objective-c to the JavaScript world.

JSConnector.h

@interface JSConnector : UIWebView

-(int)add:(int) val1 with:(int)val2;
-(void)createUser:(NSString *)name;
-(NSString *)greetCurrentUser;

@end

JSConnector.h

@implementation JSConnector

- (id)init
{
    self = [super init];
    if (self) {
        NSMutableString *pageStr = [[@"<!DOCTYPE html>\n " mutableCopy] autorelease];
        [pageStr appendString:@"<head>\n"];
        [pageStr appendString:@"<script type=\"text/javascript\" src=\"java2javascript.nocache.js\"></script>\n"];
        [pageStr appendString:@"<script>      function myInit() {alert('GWT ready to go');}</script>\n"];
        [pageStr appendString:@"</head>\n"];
        [pageStr appendString:@"<body>\n"];
        [pageStr appendString:@"</body>\n"];
        [pageStr appendString:@"</html>\n"];
        
        NSString *resourcePath = [[NSBundle mainBundle] bundlePath];
        NSURL *resourcePathURL = [NSURL fileURLWithPath:resourcePath];
        [self loadHTMLString:pageStr baseURL:resourcePathURL];
        [self setJSVariable];
    }
    return self;
}

-(void)setJSVariable{
    [self stringByEvaluatingJavaScriptFromString:@"var bl = null;"];
    [self stringByEvaluatingJavaScriptFromString:@"var helloUser = null;"];
}

-(int)add:(int) val1 with:(int)val2{
    [self runJS:@"bl = new jsc.BusinessLogic();"];
    NSString *rawJS = [NSString stringWithFormat:@"bl.addNumber(%i, %i);",val1, val2];
    NSString *addResult = [self runJS:rawJS];
    return [addResult integerValue];
}

-(void)createUser:(NSString *)name{
    [self runJS:@"bl = new jsc.BusinessLogic();"];
    NSString *rawJS = [NSString stringWithFormat:@"helloUser = bl.buildHelloUser('%@');",name];
    [self runJS:rawJS];
}

-(NSString *)greetCurrentUser{
    return [self runJS:@"helloUser.sayHi();"];
}

-(NSString *)runJS:(NSString *)js{
    return [self stringByEvaluatingJavaScriptFromString:js];
}

@end

Conclusion

Due to the leak of JVM running pure Java code in iOS is not possible. The workaround is to leverage GWT to translate the Java code into JavaScript who is more an “universal” language.

You are limited to what JavaScript can do. In some case it can be enough.
To do more than what JavaScript can do you will have to rewrite specific code in the appropriate language.

This technique open the door to much more possibility in theoretically any platform able to run javascript can now run this JavaScript library.

FYI We was able to run our “java library” on Windows Phone simply by changing the GWT compiler to “user.agent=IE9″.