Java sur iOS et Windows Phone grâce à GWT

Nous avons developer un prototype pour une entreprise désireuse de passer mobile pour iOS et Windows Phone.
Le challenge fût d’éviter de réécrire le code Java en Objective-C et C#. Du moins de l’éviter au maximum.
Le tous dans application native pour une meilleur expérience utilisateur.

Malheureusement il n’y à pas de JVM disponible sur iPhone, iPhone ni Windows Phone. Alors comment exécuter du code Java sur ces plate-forme mobile?

Ce tutorial démontre la technique pour iOS.
Il suffi de suivre la même logic pour l’adapter à Windows Phone.

Java compiler en JavaScript exécuter dans une UIWebView

De Java à iOS

De Java à iOS

L’idée est de packager le code Java dans une librarie.
Puis le compiler en JavaScript grâce à Google Web Toolkit (GWT).

En embarquant ce JavaScript générer dans notre iOS app, il est possible de crée une page web en mémoire qui l’exécute.

Objective-C peut alors appeler le “code java” par l’intermédiaire d’une UIWebView en utilisant des appelles JavaScript.
En réalité il ne s’agit pas exactement de code Java, mais de code JavaScript compiler depuis du code Java.

I – Du Java en JavaScript avec GWT


Une fois GWT pour Eclipse installer, ont peu crée une nouveau projet.
Pour ce prototype nous avons choisi d’utiliser Maven pour crée le projet GWT.

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



Exemple de configuration:
groupId: com.doduck
artifactId: java2javascript
version: 1.0-SNAPSHOT
package: com.doduck
module: java2javascript
Crée et importer le projet Eclipse:

mvn eclipse:clean eclipse:eclipse


Structure d'un projet GWT

Structure d’un projet GWT




- “client” package: Le code Java de ce package qui sera compiler en JavaScript
- java2javascript.gwt.xml: configuration du projet GWT

Pour compiler du code Java en JavaScript, il faut impérativement déplacer le code Java dans le package “client”.
Alternativement l’utilisation de dépendances de projet est possible mais il ne s’agit pas d’en discuter ici.


Gwt-exporter permet de rendre le javascript compiler “appellable” depuis du JavaScript no GWT.

Ajouter dans le pom.xml le dépendance:

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

Mettre à jour le projet Eclipse:

mvn eclipse:clean eclipse:eclipse


Editer le fichier .gwt.xml en ajoutant:

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



user.agent=safari va compiler le Java en en JavaScript pour le navigateur Safari uniquement. Pour Windows Phone changer le user.agent pour “IE9″. Le linker “sso” va forcer le la compilation en un seul fichier JavaScript.

Pour rendre le code Java “appellable” depuis du JavaScript non GWT, quelques annotation sont requise.

Pour simplifier ce prototype, prétendons cette 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 ]
}



L’annotation @Export et l’implementation “Exportable” rend la classe utilisable depuis du JavaScript natif (non GWT).

@ExportPackage(“jsc”) lui, donne un nom de package.
Il n’est pas possible d’utiliser tous les objet GWT en dehors de GWT. Par exemple il est possible d’envoyer des string[] mais pas des List<String>. C’est pourquoi la fonction “public List<String> listGretter()” est annotée @NoExport.
Afin d’utiliser ces objets il faut les encapsuler une classe exportable.

HelloUser peut être directement retourner par buildHelloUser() et manipuler par du JavaScript car il implement “Exportable” et est annotée @Export.

D’après la documentation, gwt-export support:
- Les primitive comme double, mais pas Double
- les long (à l’instare de JSNI)
- String
- java.util.Date
- Les classes qui étendent de JavaScriptObject
- Les classes ou interface qui implement Exportable
- Arrays de tous les type au dessus, mais pas les lists ou collections.

Il est possible d’envoyer des callback en paramètre pour les fonction asynchrone.
La possibilité d’utiliser des String permet l’envoi d’objet JSON…

Modifier Java2javascript.java pour activer gwt-exporter:

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

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

Le point d’entrer invoque gwt-exporter quand le module GWT est entièrement charger.
Ensuite le module appelle onLoad() si la fonction existe en JavaScript non GWT. À ce moment il est possible d’utiliser la librarie JavaScript.

Compiler en javascript grâce à Maven:

mvn clean install

Le JavaScript compiler ce trouve dans /target/java2javascript-1.0-SNAPSHOT/java2javascript/java2javascript.nocache.js
Il s’agit de code JavaScript hautement optimisé qui rend GWT si performant.

II – Tester dans Safari

Copier java2javascript.nocache.js dans un dossier et crée un fichier HTML page pour tester cette librarie JavaScript.

<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>



user.agent va forcer le compilateur GWT pour le navigateur Safari.

Par chance ce prototype fonctionner avec Chrome and FireFox mais ça ne sera pas le cas pour des projets complexe.

III – Exécuter Java GWT dans une UIWebView

Drag et dropper le fichier java2javascript.nocache.js dans le projet XCode.
Le fichier JavaScript sera ajouter dans la section “Compile Source”. Il faut le changer!


importer dans Copy Bundle resource

importer dans Copy Bundle resource






Afin d’embarquer la librairie JavaScript, il faut l’ajouter dans la section “Copy Bundle Resources”.

La dernière étape est de crée une classe pour wrapper notre librairie.
Cette interface permettra d’appeler le JavaScript depuis Objective-C.

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

A cause du manque de JVM exécuter du code Java en native n’est pas possible. La solution réside dans l’utilisation de GWT pour traduire le Java en JavaScript qui est un langage plus “universel”.

Il y à des limitation à ce que JavaScript peut faire. Dans certain cas la technologie est suffisante.
Afin de faire plus ce que JavaScript permet, il faut malheureusement écrire le code dans un langage opportunités.

Cette technique ouvre la porte a bien d’autres opportunités. En théorie, toute les plate-forme capable d’exécuter du JavaScript peuvent utiliser cette librairie JavaScript.

Nous avons été capable d’exécuter cette “librairie java” sur Windows Phone.
Il à simplement fallu changent le flag GWT compile en “user.agent=IE9″.

Laisser un commentaire