Mit JavaScript wie hausgemacht: NativeScript

NativeScript kombiniert eine plattformunabhängige Anwendungslogik mit nativen Steuerelementen. Damit wirken die Apps gefälliger als mit Web-Views, stellen aber erhöhte Anforderungen an Entwickler.

Know-how  –  3 Kommentare
Mit JavaScript wie hausgemacht: NativeScript

Die erste Generation der JavaScript-Frameworks zur mobilen Entwicklung führen Applikationen in einer vom Betriebssystem bereitgestellten Web-View aus – PhoneGap ist ein gutes Beispiel. Das eigentliche Benutzerinterface entsteht normalerweise unter Nutzung eines JavaScript-GUI-Stacks, der nativen Steuerelemente emuliert.

Zwar sieht sie auf allen Geräten gleich aus, ihre Bedienung fühlt sich aber im Vergleich zu nativen Apps fremd an. Teleriks NativeScript setzt stattdessen auf die in Abbildung 1 gezeigte Struktur.

Text Die NativeScript-Infrastruktur ist vergleichsweise komplex (Abb. 1). (Bild: Telerik)

Die JavaScript-Runtime dient in solchen Frameworks als Ausführungsumgebung für weitgehend beliebigen Code, der dank Bindings zur Geräte-API mit nativen Steuerelementen und anderen spezifischen Klassen interagieren kann. NativeScript erschien einige Wochen vor React Native, ist vom Konzept her aber im Großen und Ganzen vergleichbar.

Im Rahmen der Erstankündigung von NativeScript sprach Telerik davon, neben Android und iOS auch Windows Phone unterstützen zu wollen. Am 17. März haben die Macher eine Version für die Universal Windows Platform bereitgestellt, die seither allerdings nicht wesentlich weiterentwickelt wurde.

Das bulgarische Unternehmen Telerik gehört inzwischen zur Progress-Gruppe und ist als Beratungsunternehmen und für diverse JavaScript-GUI-Stacks bekannt. Der wichtigste Grund dafür, dass das Unternehmen sein Produkt kostenlos zur Verfügung stellt, dürfte die IDE namens AppBuilder sein. Nach einer anfänglichen Testphase ist ihre Nutzung kostenpflichtig. Wer mit NativeScript komfortabel in einer GUI arbeiten möchte, muss somit an anderer Stelle etwas bezahlen. Da Tutorials zur Nutzung von AppBuilder im Zusammenspiel mit NativeScript reichlich verfügbar sind, verwendet dieser Artikel die Kombination.

NativeScript existiert in der Node.js-Ausführungsumgebung. Die folgenden Schritte erfolgten auf einer 64-Bit-Workstation mit Ubuntu 14.04 LTS. Die Abfrage der Node-Version ergab folgendes Ergebnis:

tamhan@tamhan-thinkpad:~$ nodejs -v v4.4.5

Zum Ergänzen der Node-Installation um das NativeScript-Paket dient folgender Befehl:

sudo npm install nativescript -g --unsafe-perm

Die folgenden Schritte gehen davon aus, dass auf der Entwicklermaschine eine vergleichsweise aktuelle und voll funktionsfähige Version von Android Studio samt SDK läuft. Leider legt Android Studio die für die Arbeit mit NativeScript zwingend erforderliche Umgebungsvariable ANDROID_HOME nicht automatisch an. Das lässt sich in der Kommandozeile durch Bearbeiten der bashrc-Datei beheben:

export ANDROID_HOME=/home/NUTZER/Android/Sdk 
export PATH=$PATH:$ANDROID_HOME:/home/NUTZER/Android/Sdk/tools/
export PATH=$PATH:$ANDROID_HOME:/home/NUTZER/Android/Sdk/platform-tools

Entwickler müssen NUTZER durch den Nutzernamen ihres Kontos ersetzen. Nach dem Abarbeiten der Kommandozeilenbefehle öffnet sich bashrc in gedit. Ans Ende der Datei gehört nun die oben aufgeführte Variablendeklaration.

~/Desktop cd ~
~$ gedit .bashrc

Nach einem Neustart des Terminals lassen sich benötigte Komponenten mit folgendem Kommando herunterladen:

sudo $ANDROID_HOME/tools/android update sdk \ 
--filter tools,platform-tools,android-23, \
build-tools-23.0.2,extra-android-m2repository, \
extra-google-m2repository,extra-android-support \
--all --no-ui

Im Fall von Konfigurationsfehlern gibt es mit tns doctor ein Werkzeug, das nach Problemen sucht.

Nach dem Durchlauf von tns doctor ist es an der Zeit, ein erstes Sample zu generieren. Dazu dient der Befehl tns create, der als einzigen Parameter den Namen der zu erzeugenden Applikation übernimmt:

tns create HeiseSample

Nach der erfolgreichen Ausführung findet sich im aktuellen Arbeitsverzeichnis ein Unterordner mit dem Projektnamen, der eine vergleichsweise komplexe und aus rund fünfhundert Dateien bestehende Struktur enthält. Im Ordner App finden sich die für die Applikation selbst notwendigen Ressourcen. Im konkreten Fall gibt es neben dem Einsprungspunkt app.js und einigen globalen Ressourcen zusätzliche Elemente, die für die Realisierung des Hauptformulars zuständigen sind:

----app
| |---app.css
| |---app.js
| |---App_Resources
| | |---Android
| | | |---AndroidManifest.xml
| | | |---app.gradle
| | | |---drawable-hdpi
| | | | |---icon.png
...
| | |---iOS
| | | |---Info.plst
...
| |---main-page.js
| |---main-page.xml
| |---main-view-model.js
| |---package.json
| |---node_modules

Das Unterverzeichnis node_modules enthält JavaScript-Erweiterungsbibliotheken, die zunächst nicht von Belang sind.

| |---platforms

platforms ist anfangs leer. Es wird im Laufe der Zeit mit nativen Elementen ergänzt werden, ist aber im Großen und Ganzen ein Arbeitsverzeichnis der Runtime, in dem jederzeit mit Eingriffen und Überschreibungen von Änderungen zu rechnen ist.

JavaScript-Frameworks der ersten Generation wie PhoneGap leiden darunter, dass die mit ihnen erzeugten Applikationen nicht besonders nativ aussehen.

Frameworks wie Teleriks Kendo UI oder jQuery UI versuchen, diesen Missstand durch CSS-Tricks zu beheben, aber ein Nachbau kann – wie in der Einleitung erwähnt – nie mit dem Original mithalten. NativeScript umgeht das Problem auf eine innovative Art und Weise: Die Formulare entstehen zur Laufzeit durch das Parsen einer xml-Datei. Im Framework findet sich eine Engine, die die einzelnen Kinder-Tags mit nativen Steuerelementen verdrahtet. Das Beispielformular realisiert eine Seite mit drei übereinander angeordneten Steuerelementen: Neben zwei Labels gibt es einen Button, der beim Anklicken eine Aktion auslösen soll. In XML sieht das folgendermaßen aus:

<Page xmlns="http://schemas.nativescript.org/tns.xsd" 
navigatingTo="onNavigatingTo">
<StackLayout>
<Label text="Tap the button" class="title"/>
<Button text="TAP" tap="{{ onTap }}" />
<Label text="{{ message }}" class="message"
textWrap="true"/>
</StackLayout>
</Page>

In der Datei main-view.js findet sich die für die Initialisierung des Formulars notwendige Logik: NativeScript implementiert Data Binding, weshalb sich der Code im Großen und Ganzen auf das Anlegen eines ViewModels beschränkt:

var createViewModel =
require("./main-view-model").createViewModel;
function onNavigatingTo(args) {
var page = args.object;
page.bindingContext = createViewModel();
}
exports.onNavigatingTo = onNavigatingTo;

Der interessantere Code befindet sich derweil in der Datei main-view-model.js und sieht folgendermaßen aus:

var Observable = require("data/observable").Observable;
function getMessage(counter) {
if (counter <= 0) {
return "Hoorraaay! You unlocked the " +
"NativeScript clicker achievement!";
}
else {
return counter + " taps left";
}
}
function createViewModel() {
var viewModel = new Observable();
viewModel.counter = 42;
viewModel.message = getMessage(viewModel.counter);
  viewModel.onTap = function() {
this.counter--;
this.set("message", getMessage(this.counter));
}
  return viewModel;
}
exports.createViewModel = createViewModel;

createViewModel liefert ein Steuerelement zurück, das die für das Data Binding ansprechbaren Methoden exponiert. Die Exports-Deklaration verhält sich im Großen und Ganzen analog zu anderen Node.js-basierten Systemen.