RoboVM: Mit Java iOS-Applikationen erstellen
Seite 2: Aufbau und Dialoge
Eine langwierige Geburt
Außer dem in Eclipse eingebundenen Plug-in sind für die Entwicklung ein Apple-Rechner und eine aktuelle Version von Xcode nötig. Das Plug-in enthält in der derzeitigen Version noch keine Anbindung für den Interface Builder der Entwicklungsumgebung. Den Äußerungen der Macher von RoboVM ist jedoch zu entnehmen, dass eine solche geplant ist.
Das Projekt soll in Java analog zu der für den Vergleich zwischen Xamarin und Delphi entstandenen nativen Xamarin-iOS-Implementierung aufgebaut werden. In deren Zentrum steht ein sogenanntes Storyboard. In ihm kann man das gesamte Layout inklusive automatisierter und programmgesteuerter Übergänge und Verbindungen zwischen den einzelnen Fenstern einer dialogorientierten iOS-App definieren.
Leider unterstützt RoboVM in der vorliegenden Version diese seit einiger Zeit etablierte Funktion noch nicht. Daher muss die Anwendung die ältere Form des Einzel-Dialog-Designs, den XIB (Mac OS X Interface Builder), verwenden. Eine kleine Beispiel-App aus dem Blog der RoboVM-Website zeigt, wie man dabei vorgehen muss. Leider ist das Beispiel aus dem Jahr 2013 und wurde nicht aktualisiert, weshalb dort eine sehr frühe Alpha-Version von RoboVM zum Einsatz kam. Seitdem unterlagen die APIs und Annotationen weitreichenden Veränderungen, auch was die Namensgebung und die zugrunde liegenden Datentypen angeht. Möchte man die App zum Laufen bekommen, ist an der Stelle einiges anzupassen, was nicht gerade aufbauend ist, da man im Normalfall lauffähige Beispiele erwartet.
Beginnt man nun die Programmierung, muss man berücksichtigen, dass die Hauptklasse sich im Default-Package befindet. Weitere Klassen lassen sich auf sinnvoll benannte Pakete verteilen. Der Einfachheit halber und auch wegen der App-Größe bleibt das vorliegende Beispiel in dem Fall komplett beim Default-Package.
import org.robovm.apple.foundation.NSAutoreleasePool;
import org.robovm.apple.uikit.UIApplication;
import org.robovm.apple.uikit.UIApplicationDelegateAdapter;
import org.robovm.apple.uikit.UIApplicationLaunchOptions;
import org.robovm.apple.uikit.UIScreen;
import org.robovm.apple.uikit.UIWindow;
public class XibTestMain extends UIApplicationDelegateAdapter {
private UIWindow window = null;
private XibTestViewController viewController = null;
@Override
public boolean didFinishLaunching(UIApplication application,
UIApplicationLaunchOptions launchOptions) {
window = new UIWindow(UIScreen.getMainScreen().getBounds());
window.setAutoresizesSubviews(true);
viewController = new XibTestViewController();
window.setRootViewController(viewController);
window.makeKeyAndVisible();
return true;
}
public static void main(String[] args) {
@SuppressWarnings("resource")
NSAutoreleasePool pool = new NSAutoreleasePool();
UIApplication.main(args, null, XibTestMain.class);
pool.drain();
}
}
Die Hauptklasse ist an sich sehr rudimentär aufgebaut und ruft den ersten View Controller auf. Der wiederum baut aus der im Interface Builder definierten Datei einen Dialog auf und zeigt ihn anschließend an.
Dialoge entwerfen
Für das Design der Dialoge ist in Xcode ein paralleles Projekt zu erstellen. Dabei ist es unerheblich, ob man eine Swift- oder eine Objective-C-Applikation ausgewählt hat. Wichtiger ist die Verbindung zwischen dem aus dem Interface Builder hervorgegangenen XML und dem Java-Code. Hierzu müssen Entwickler eine Klasse (bei Swift) oder eine Header-Datei (bei Objective-C) mit dem gleichen Namen wie die korrespondierende Java-View-Klasse erstellen. Dort werden in üblicher Xcode-Manier die Actions und Outlets mit den Gegenstücken des Layouts verbunden beziehungsweise aus ihnen heraus definiert.
Die so vorbereitete Objective-C- oder Swift-Klasse wird als FileOwner in der entsprechenden XIB-Definition eingetragen. Man sollte nicht vergessen, die implizite View Property des Controllers mit der View aus der XIB-Definition zu verbinden, was leider nicht beschrieben wurde.
AnschlieĂźend werden die korrespondierende Methoden in der Java-Klasse implementiert und durch Annotationen den Actions und Outlets zugeordnet.
Das lässt sich über die @BindSelector-Annotation umsetzen, die die korrespondierenden Labels aus dem Interface Builder referenziert. Dabei ist die Namensgebung des Interface Builder ausschlaggebend. Sollten weitere Parameter folgen, hängt das Tool einen Doppelpunkt an den Namen und erweitert ihn gegebenenfalls um ein "forEvent:".
Diese Annotationen werden noch um eine weitere ergänzt: @Callback. Die entsprechend ausgezeichneten Methoden werden als static implementiert, den Kontext der aktuellen View-Controller-Klasse bekommen sie über einen self-Parameter. Damit kann man aus der Action-Methode auf die Daten der aktuellen Instanz zugreifen. Der self-Parameter muss zusätzlich zum Selector, dem auslösenden Steuerelement und den optionalen Parametern in die Parameterliste aufgenommen werden.
@Callback
@BindSelector("divideButtonClicked:")
private static void divideButtonClicked(XibTestViewController self,
Selector sel, UIButton button) {
self.clicked(button);
}
Ähnlich ist es, wenn man Outlets definieren möchte. Der Ausdruck bezeichnet im Apple-Jargon die Komponenten-Referenzen. Hierfür gibt es bisher keine expliziten Annotationen (auch diese sollten mal später folgen), weshalb man einen Setter mit dem Komponentennamen implementieren muss.
Bei einer derartigen Implementierung wird eine weitere Annotation benötigt, die die gegebenenfalls erforderliche Datentypen-Umsetzung beschreibt. Leider ist auch in dem Fall die Dokumentation recht dürftig.
@Callback
@BindSelector("setFirstNumberTextField:")
@TypeEncoding("v@:@")
private static void setFirstNumberTextField(XibTestViewController self,
Selector sel, UITextField textField) {
self.firstNumberTextField = textField;
}
Dieses Verfahren ist leider aufwendig und fehleranfällig, da man die ganzen Schnittstellen zum Layout an zwei Orten parallel pflegen muss. Zudem sind alle Änderungen sowie Erweiterungen, die an einer Stelle (XIB-Datei, Objective-C/Swift-Klasse) gemacht wurden, an der anderen (im Java-Code) ebenfalls umzusetzen.
Will man anschlieĂźend das Programm bauen und starten, muss man vorher in einem manuellen Prozess durch den Aufruf des Interface Builder Compiler die .XIB-Datei in eine des Formats .NIB umwandeln. Sie wird anschlieĂźend in den Resource-Zweig eingesetzt.