Monthly Archives: April 2010

Titanium Charts mit Webkit

In meiner App möchte ich ein Diagramm darstellen und benötige dafür eine Möglichkeit 2D Grafiken zu erzeugen. Die API von Titanium bietet derzeit keine Möglichkeit dies zu tun, weshalb ich mir überlegt habe WebKit zu nutzen und dann auf einem Canvas zu zeichnen. Die ersten Versuche im iPhone Simulator waren sehr vielversprechend, aber leider wird der Code auch mit der Geschwindigkeit meines MacBooks ausgeführt. Die Performanz, mit der die Diagramme auf dem Gerät erzeugt werden, ist eher ernüchternd.

Bevor ich auf die einzelnen Werte eingehe, erkläre ich erstmal einen möglichen Weg Diagramme mit Titanium darzustellen. Meine Ordnerstruktur sieht wie im folgenden Bild aus:
bildschirmfoto-2010-04-15-um-084628

Die Titanium Skripte liegen bei mir alle in der Wurzel. In diesem Fall ist es nur die app.js. Sie ist einfach aufgebaut und erzeugt nur eine WebView, in der eine URL geladen wird.

1
2
3
4
5
6
7
8
9
10
var window = Ti.UI.createWindow({
  orientationModes: [Ti.UI.LANDSCAPE_RIGHT]
});
 
var webkit = Ti.UI.createWebView({
  url: 'web/chart.html'
});
 
window.add(webkit);
window.open();

Die WebView Komponente lädt eine lokale HTML Seite. Die HTML Seiten befinden sich in dem Ordner web und relativ dazu liegen die Skripte und die Stylesheets. Ein wesentlicher Unterschied zu einer normalen Webanwendung ist die Benennung der JavaScript Dateien. Sie enden bei mir mit *.javascript. Titanium liest in seinem Build-Vorgang rekursiv alle Dateien mit der Endung *.js ein und nimmt an, dass dies Titanium Skripte sein müssen. Der Parser geht strikt vor und erlaubt keine Fehler. In Bibliotheken wie jQuery oder Prototype fehlt aber ab und an ein Semikolon. Damit kommt Titanium nicht klar. Eine Möglichkeit das Problem zu umgehen ist die Umbenennung der Endung. Auf diese Weise müssen externe Bibliotheken nicht in die HTML Seiten eingebettet werden.

Wie zu sehen ist, nutze ich für dieses Beispiel Flot. Ein jQuery Plugin zur Erzeugung von Diagrammen. Dafür muss zuerst jQuery, dann Flot, dann mein Code geladen werden. Die HTML Datei, die die WebView Komponente also lädt, sieht wie folgt aus:

1
2
3
4
5
6
7
8
9
10
11
12
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="css/style.css" />
    <script type="text/javascript" src="scripts/jquery.min.javascript"></script>
    <script type="text/javascript" src="scripts/jquery.flot.min.javascript"></script>
    <script type="text/javascript" src="scripts/chart.javascript"></script>
  </head>
  <body>
    <!-- Render the chart in this container -->
    <div id="placeholder"></div>
  </body>
</html>

Was nun fehlt ist die Brücke zwischen Flot in der WebView und Titanium. Das Schöne ist, dass die WebView eine Methode bereitstellt, mit der sich JavaScript Code evaluieren lässt: evalJS. Ich bevorzuge den JavaScript Code, der für die WebView gedacht ist, an einer Stelle zu halten und habe daher mir die chart.javascript angelegt:

1
2
3
4
5
6
7
8
9
10
function runChartDemo() {
  var d1 = [];
  for (var i = 0; i < 14; i += 0.5) {
    d1.push([i, Math.sin(i)]);
  }
  var d2 = [[0, 3], [4, 8], [8, 5], [9, 13]];
  var d3 = [ [0, 12], [7, 12], null, [7, 2.5], [12, 2.5]];
 
  $.plot($("#placeholder"), [ d1, d2, d3 ]);
}

Die runChartDemo() Funktion steht mir spätestens dann zu Verfügung, wenn die WebView geladen und Einsatzbereit ist. Und das bekomme ich in Titanium durch einen EventListener mitgeteilt. Nebenbei: In den Docs steht geschrieben, dass entweder nur die Titanium Events oder die Events in dem JavaScript Universum der WebView genutzt werden sollen. Nach Möglichkeit nicht gemischt. Auf jeden Fall kann ich nun in der app.js das load-Event der WebView abgreifen und meine Funktionen ausführen:

1
2
3
webkit.addEventListener('load', function() {
  webkit.evalJS('runChartDemo()');
});

Starte ich Titanium, sehe ich das folgende Ergebnis:bildschirmfoto-2010-04-15-um-1004111

Nun zu den Nachteilen. Es ist langsam. Zumindest so langsam, dass es mir als Anwender keinen Spass machen wird, auf die Grafik zu warten. Alleine die nackte WebView wird auf meinem iPhone 3G in ca. 1000ms erzeugt. Dann wird jQuery und Flot geladen. Wie lange das dauert, hängt davon ab, ob es zum ersten mal in der App passiert oder zum x-ten mal (das iPhone scheint das zu cachen). Ich habe Werte zwischen 2000ms und 8000ms gehabt. Dann wird das Diagramm erzeugt. Ist es ein einfaches, dauert die Erzeugung 800ms, bei einem komplexeren mit vielen Daten auch schonmal 2000ms. Soll in der Summe heissen, dass diese Lösung im besten Fall nach 3500ms und im schlimmsten Fall nach 11000ms ein Diagramm zeigt.

Ich habe einige Diagramm-Bibliotheken gefunden, aber die meisten, die von der Optik in Betracht kommen, basieren auf einer anderen Bibliothek wie jQuery, MochiKit oder MooTools. Für mich persönlich sind 3500ms akzeptabel, aber 11000ms nicht. Deshalb denke ich, dass noch einiges an Forschungsbedarf auf mich zukommt.

Einstieg in Appcelerator Titanium

Christian hat in einem Artikel das Titanium Framework von Appcelerator erwähnt. Ich kannte es vorher nicht und habe es mit die letzten Tage einwenig näher angesehen. Aktuell habe ich ein konkretes Projekt, das ich eigentlich wieder mit GWT umsetzen wollte, aber Titanium scheint mir eine echte, und vielleicht sogar bessere Alternative zu sein.

In Titanium wird der Programmcode in JavaScript geschrieben und als nativer Code auf der jeweiligen Platform (Android oder iPhone) ausgeführt. Das ist der große Vorteil. Während ich den Code nur einmal schreibe, ist er dank dem Framework auf mehreren Plattformen lauffähig.

Titanium übernimmt dabei die Rolle eines Proxies. Während bei GWT der Java Code von einem echten Cross-Compiler in JavaScript Code übersetzt wird, interpretiert Titanium den JavaScript Code auf der jeweiligen Plattform und delegiert die Funktionsaufrufe an Module. Diese Module setzen dann die Funktionsaufrufe nativ um. An sich kein schlechter Ansatz. Doch der Code wird interpretiert, deshalb ist er nicht sonderlich schnell.

Das Ergebnis ist, dass mit relativ wenig Code Standardfunktionalität umgesetzt werden kann. Standard deshalb, weil natürlich nur die Funktionen genutzt werden können, die dem Titanium Framework als Modul zu Verfügung stehen. Möchte der Entwickler Funktionen nutzen, die es in Titanium nicht gibt, kann er eigene Module implementieren und damit Titanium erweitern.

Ich habe beim Entwickeln sowohl ein freudiges Grinsen als auch Verzweiflung im Gesicht gehabt und bis jetzt bin ich mir immer noch nicht schlüssig.

Vorteile

  • Schnelle Erfolgserlebnisse und Ergebnisse
  • Code ist komplett in JavaScript. Daher kein Erlernen einer neuen Sprache
  • Native Anwendung
  • Großer Funktionsumfang, viele Module
  • Automatisches Deployment auf das Gerät und den Simulator
  • Schnelles Bauen, da nur wenig übersetzt werden muss
  • Konsole mit Log-Ausgaben

Nachteile

  • Keine Möglichkeit zum Debuggen Ich schreibe den Code in IDEA, wechsele dann in den Titanium Developer und führe mein Programm aus. Um den Code zu Debuggen, müsste es eine Integrationsmöglichkeit für die IDE geben. Da Titanium noch sehr jung ist, kann es aber durchaus sein, dass dies bald kommt.
  • Wenig Transparenz Bis auf die Log-Ausgaben sehe ich leider nicht, was mein Programm macht. Manchmal ist mein Simulator abgestürzt oder startete das Programm nicht. In vielen Fällen gab es keinen Anhaltspunkt zu der Ursache. Ein exit(null) reicht als Fehlerbeschreibung nicht.
  • Hohe Fehlertoleranz, wenig Feedback In Titanium werden statische „create“ Methoden genutzt, um Instanzen anzulegen. Diese Instanzen werden durch Parameter konfiguriert. z.B.

    1
    2
    3
    4
    5
    6
    
    var view = Titanium.UI.createView({
       borderRadius:10,
       backgroundColor:'red',
       width:50,
       height:50
    });

    Titanium schluckt fast alles. Es gibt leider keine Überprüfung darüber, ob ein Parameter erlaubt ist, oder ob er einen gültigen Wert bekommen hat. Dadurch weis ich als Entwickler nicht, aber ich das Objekt falsch konfiguriert habe, oder ob der Fehler eine andere Ursache hat.

  • Organisation von JavaScript Dateien in Ordnern ist zwar möglich, bringt aber Probleme mit sich. Ich habe meine „Views“ in einem Unterordner liegen gehabt. Relativ zu der app.js. Die Dateien wurden alle richtig gefunden und interpretiert. Als ich das Programm jedoch ausgeführt, von einem Fenster zu einem anderen und wieder zurück, navigiert habe, wurde auf einmal der Pfad zu der JavaScript Datei falsch zusammengebaut. Daraufhin stürzte mein Programm ab.
  • Performanz Obwohl die Ausführungszeit nicht schlecht ist, gibt es einige Stolperfallen. Beispiel TableView. In der createTableView Methode wird ein Array mit den Zeilen der Tabelle übergeben. Diese Zeilen sollten zuvor erzeugt und dann der TableView auf einen Schlag übergeben werden – was auch klappt. Leider ist die Erzeugung der Zeilen in einer Schleife alles andere als performant. Handelt es sich um einfache Texte, dann geht dies relativ zügig. In meinem Fall bestand eine Zeile aus einer View mit drei Labels. Für die Erzeugung von nur 25 Zeilen mit statischem Inhalt hat auf meinem iPhone 3g 2-3 Sekunden gedauert. Kommen die Daten von einem Server, muss die Latenz und Serverzeit addiert werden. Ich kann mir vorstellen, dass sich dieser Wert verdoppeln kann.

Insgesamt kann ich aber sagen, dass die Vorteile knapp überwiegen, so dass ich auf jeden Fall meine aktuelle App mit Titanium realisieren werde.