zurück zum Artikel

Mit JavaScript wie hausgemacht: NativeScript

Know-how
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.

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.

Telerik
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 [1].

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 [2] bereitgestellt, die seither allerdings nicht wesentlich weiterentwickelt wurde.

Wie und Warum

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.

Beispielhaft erzeugt

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.

Oberflächlich aufgebaut

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.

Native Umsetzung

Und nun nativ

Das NativeScript-Projekt ist damit zwar technisch komplett, aber noch nicht ausführbar. Das liegt daran, dass es noch plattformagnostisch ist. Um die konkrete Anbindung an eine Plattform einzufügen dient wieder ein im Projektordner ausgeführter TNS-Befehl, der für Android folgendermaßen aussieht: tns platform add android.

Nach Eingabe des Befehls muss NativeScript einige Megabyte an Daten von einem langsamen Server herunterladen. Die Abarbeitung nimmt somit einige Zeit in Anspruch.

Gelegentlich stellt add plattform die Arbeit kommentarlos ein, wenn der Befehl mit seiner Ausführungsumgebung unzufrieden ist. Nach erfolgreicher Ausführung weist der Unterordner Plattforms ein Verzeichnis namens Android auf, in dem sich ein komplettes Android-Projekt befindet.

Damit ist der Code bereit zur Installation. Die in NativeScript implementierte CLI nimmt Entwicklern die Arbeit ab: Das Kommando tns device listet alle mit der Workstation verbundenen Telefone auf.

Das Xiaomi RedMi 3 des Autors wurde erkannt (Abb. 2).
Das Xiaomi RedMi 3 des Autors wurde erkannt (Abb. 2).

Der Befehl tns run android installiert die Anwendung. Beim ersten Aufruf erfolgt der Download eines rund 20 MByte großen Archivs mit dem Gradle-Buildsystem von einem Telerik-Server.

Während der Programmausführung gibt tns run android Logdaten aus, die Entwickler über den Console.log-Befehl um eigene Meldungen ergänzen können. In der praktischen Nutzung ist run android nur für die Erstauslieferung der Applikationen verantwortlich. Zudem kommt das Kommando zum Einsatz, wenn sich der native Unterbau wie die Manifestdatei ändern.

Für Änderungen im JavaScript- oder XML-Teil dient im Normalfall der im NativeScript implementierte LiveSync-Dienst. Er erkennt Änderungen im Dateisystem und leitet sie an das Telefon weiter. Seine Aktivierung erfolgt durch die Eingabe von tns livesync --watch – einen eventuell laufenden tns run android-Befehl beendet zuvor der Druck auf Ctrl+C.

Das funktioniert jedoch nicht unter Android 4.3. Wer LiveSync nutzen möchte, muss auf Android 4.4 umsteigen. Auch einige Samsung-Telefone verweigern die Kooperation. In beiden Fällen schafft tns run Abhilfe.

Formular im Austausch

Der nächste Schritt ist ein Formularwechsel. Dazu benötigt die Applikation die folgenden drei zusätzliche Dateien:

- work-page.js
- work-page.xml
- work-view-model.js

Dabei darf es sich um Kopien des vorhandenen Formulars handeln. Der nächste Schritt passt den Inhalt von main-page.xml folgendermaßen an, um zwei Textfelder zu erzeugen:

  <StackLayout>
<TextField id="email" hint="Email Address"
keyboardType="email" autocorrect="false" />
<TextField id="password" hint="Password"
autocorrect="false" />
<Button text="Login" tap="{{ onTap }}" />
</StackLayout>

Interessant ist dabei vor allem das bisher noch nicht vorgestellte ID-Attribut. Es dient dem Auffinden des Steuerelements zur Laufzeit und gibt einen Verweis auf die jeweilige Instanz zurück.

Wer das Programm in der vorliegenden Form durch Speichern der Datei aktualisiert, erhält den in Abbildung 3 gezeigten Formularaufbau.

Der Log-in-Button ist am Platz (Abb. 3).
Der Log-in-Button ist am Platz (Abb. 3).

Für die eigentliche Aktivierung des Formulars ist eine Änderung im Modell der Haupt-View erforderlich. Der relevante Teil von main-view-model.js sieht anschließend folgendermaßen aus:

  var frameModule = require("ui/frame");
...
  viewModel.onTap = function() {
var topmost = frameModule.topmost();
topmost.navigate("work-page");
}

NativeScript stellt Formulare in Containern dar, die Frame heißen. Die Routine ergreift den sichtbaren Container, um ihn mit neuem Inhalt zu füllen. Für die Beschaffung des statischen frameModule-Objekts kommt die von anderen Node-Frameworks bekannte require()-Methode zum Einsatz. Damit ändert das Programm das aktive Formular, wenn ein Nutzer den Login-Button klickt.

An dieser Stelle gibt es zudem die Möglichkeit, einen kleinen Blick auf das in NativeScript implementierte Fehlerhandhabungssystem zu werfen. Die Suche nach der neuen Datei erfolgt durch einen klassischen Dateinamensvergleich: Wenn die Runtime die betreffende Datei nicht findet, handelt es sich um einen gravierenden Fehler. NativeScript reagiert mit dem in Abbildung 4 gezeigten Bildschirm. Wer den kompletten Stacktrace einsehen will, muss ihn in die Zwischenablage kopieren und in einen beliebigen Texteditor einfügen.

Diese NativeScript-App kann nicht weiterlaufen (Abb. 4).
Diese NativeScript-App kann nicht weiterlaufen (Abb. 4).

Die in den beiden Textfeldern eingegebenen Werte stehen der Engine – nicht jedoch dem nächsten Formular – zur Verfügung. Die Übergabe lässt sich durch Anpassung des Navigationsbefehls erreichen. Er nutzt nun statt des Dateinamens ein komplexes Objekt, das zusätzlich Kontextparameter übergeben kann:

var viewModule = require("ui/core/view");
var email;
var password;
function createViewModel() {
var viewModel = new Observable();
  viewModel.onTap = function() {
var topmost = frameModule.topmost();
email = viewModule.getViewById(topmost, "email");
password = viewModule.getViewById(topmost, "password");

Interessant ist in dem Beispiel die von Android bekannte Nutzung der Methode getViewById. Sie nimmt einen Suchkontext und eine ID entgegen und liefert im Erfolgsfall einen Verweis auf das dazu passende Steuerelement. Das Beispielformular nutzt die Informationen zum Füllen des Kontextobjekts:

    var navigationEntry = { 
moduleName: "work-page",
context : {
emailPar : email.text,
passwordPar: password.text
}
};
    topmost.navigate(navigationEntry); 
}
return viewModel;
}

Da das manuelle Herumschieben von Steuerlementinformationen nicht praktikabel ist, kommt Data Binding zum Einsatz. Für die Engine relevante Attribute werden in NativeScript durch zwei geschweiften Klammern angezeigt – der Korpus von work-page.xml verbindet die Texte der beiden Labels mit den Attributen, die in der Engine liegen:

<StackLayout> 
<Label text="{{email}}" />
<Label text="{{password}}" />
</StackLayout>

Zum besseren Verständnis folgt nun ein kleiner Umweg: Der Code von work-view.js bekommt einen Handler für das navigateTo-Event, der das Kontextobjekt und somit den vom anderen Formular übergebenen Parametern erhält:

var createWorkModel = 
require("./work-view-model").createWorkModel;
function onNavigatingTo(args) {
var page = args.object;
page.bindingContext = createWorkModel();
page.on("navigatedTo", function (eventData) {
page.bindingContext.email = page.navigationContext.emailPar;
page.bindingContext.password =
page.navigationContext.passwordPar;
  });
}
exports.onNavigatingTo = onNavigatingTo;

Die in der Datei work-view-model.js liegende Modellklasse hat die Aufgabe, das Modell zu konfigurieren. Für das Beispiel erhält sie den Wert 42 – er ist während der Programmausführung leicht erkennbar:

var Observable = require("data/observable").Observable;
function createWorkModel() {
var viewModel = new Observable();
viewModel.email = 42;
viewModel.password = 42;
  return viewModel;
}
exports.createWorkModel = createWorkModel;

Damit ist die Applikation rein theoretisch bereit zum Ausführen. Nach der Installation auf dem Telefon und der Eingabe beliebiger Werte in die Textboxen, erscheint trotzdem immer das in Abbildung 5 gezeigte Ergebnis.

Wo sind die vom Nutzer angelieferten Ergebnisse geblieben (Abb. 5)?
Wo sind die vom Nutzer angelieferten Ergebnisse geblieben (Abb. 5)?

Data Binding

Fortgeschrittenes Data Binding

Die Implementierung des Data Binding setzt das Vorhandensein einer Listener-Infrastruktur voraus, die dem Client das Überwachen von Änderungen der Variableninhalte ermöglicht. Im JavaScript-Sprachstandard ist das nicht vorgesehen, weshalb viele Frameworks ihre eigene Observable-Klasse mitbringen – das mittlerweile in der Versenkung verschwundene Knockout.js war der Vorreiter dafür.

Im Fall von NativeScript ist ein Observable ein Objekt, das ein oder mehrere Attribute unter Verwaltung hat. Die neue Version von WorkPage berücksichtigt das und schreibt die Attribute unter Nutzung der Set-Methode in das Observable ein

var createWorkModel = 
require("./work-view-model").createWorkModel;
...
  page.on("navigatedTo", function (eventData) {
page.bindingContext.set("email",
page.navigationContext.emailPar);
page.bindingContext.set("password",
page.navigationContext.passwordPar);
});
}
exports.onNavigatingTo = onNavigatingTo;

Damit ist das Programm zur Ausführung bereit. Abbildung 6 zeigt, das zu erwartende Resultat.

Das Observable funktioniert (Abb. 6).
Das Observable funktioniert (Abb. 6).

Das Auslesen von Werten in einem Observable erfolgt durch Nutzung der get-Methode:

var person = new observable.Observable(json);
var name = person.get("Name");
var age = person.get("Age");
...

Mehr Design

Seit dem Erfolg von Apples iPhone müssen Applikationen nicht nur funktionieren, sondern auch gut aussehen. NativeScript möchte Entwicklern für die Gestaltung visueller Informationen eine plattformübergreifende Vorgehensweise bieten.

Dafür nutzt es CSS, dessen Deklarationen in einem dreistufigen Baum stehen: Nach einem globalen Objekt gibt es für jedes Formular eine CSS-Gruppe, unter der die elementspezifischen Deklarationen stehen. Wie im Browser gilt auch hier, dass tiefere Elemente höher stehende überschreiben.

Das an oberster Stelle stehende Objekt lädt den Inhalt der der Datei app.css. Für das Beispiel, erhält sie folgende Deklaration:

Page {
background-color: green;
font-size: 17;
}
...

Erwartungsgemäß gilt der Code für alle Page-Instanzen, denen er eine grüne Hintergrundfarbe zuweist. Wer das Programm im vorliegenden Zustand ausführt, sieht, dass die App erwartungsgemäß funktioniert, wie die Abbildungen 7 und 8 zeigen.

Mit JavaScript wie hausgemacht: NativeScript
Mit JavaScript wie hausgemacht: NativeScript

Damit nicht alle Formulare der Heise-Beispiel-App grün sind, erzeugen Entwickler im nächsten Schritt eine Datei namens main-page.css. Wichtig ist, dass die XML- und die CSS-Datei vor der Endung den exakt gleichen Namen aufweisen. Die neue Datei erhält nun folgende CSS-Deklaration:

Page {
background-color: red;
font-size: 17;
}
Das Hauptformular ist rot geworden (Abb. 9).
Das Hauptformular ist rot geworden (Abb. 9).

NativeScript kennt nicht den vollen Funktionsumfang von CSS. Telerik bietet eine Liste [3] der Elemente, Eigenschaften und Selektoren an, die die Sprache unterstützt. Insbesondere unter Android ist das endgültige Aussehen der Steuerelemente zudem abhängig vom Gerätehersteller.

Eine Frage des Zugangs

Die Realisierung nativer Komponenten in React Native ist vergleichsweise einfach: Entwickler erzeugen die Klasse und melden sie bei der Runtime an. Interaktionen zwischen JavaScript und nativem Code erfolgen über ein klares Interface.

NativeScript verwischt diese Grenzen insofern, als das Transpilieren auf Funktions- und Objektebene erfolgt. In der Dokumentation findet sich zur Illustration der Vorgehensweise folgendes Snippet:


var Button = android.widget.Button;
var context = ...;
var btn = new Button(context);

Von Haus aus exponiert NativeScript Androids API-Level 17, also Android 4.2 vom November 2012.

Wer höhere Versionen ansprechen möchte, muss das Verhalten des Transpilers anpassen und neue Metadaten generieren. Das Erzeugen einer neuen nativen Framework-Klasse sorgt im Hintergrund für die Entstehung zweier Klassen: eine Instanz der eigentlichen Klasse auf nativer Seite und eine Proxyklasse für JavaScript.

Auch wenn NativeScript den Gutteil der Transpilation erledigt, gibt es schwierige Grenzfälle. Die Transpilationsbeziehungen listet die Dokumentation für den Schritt von JavaScript nach Java [4] und den umgekehrten von Java nach JavaScript [5] auf.

Entwickler sollten die Dateien des platform-Verzeichnisses übrigens niemals von Hand bearbeiten. Die NativeScript-Runtime ersetzt sie regelmäßig durch ihre Pendants im Ordner /app.

Vor dem Beginn der praktischen Übungen sei noch darauf hingewiesen, dass das Exponieren kompletter Steuerelemente für den XML-Parser möglich ist. Weitere Informationen dazu finden sich im Beispiel [6] innerhalb der Dokumentation.

Bibliotheken und Fazit

Mach die Bibliothek

Bibliotheken lassen sich komfortabel einbinden, wenn sie per Gradle verfügbar sind. Da das in der Praxis nicht immer der Fall ist, stellen .jar-Dateien eine attraktive Alternative dar. Die Arbeiten an der letzten Übung dieses Artikels setzen eine eigene Bibliothek voraus.

Zunächst öffnen Entwickler dafür Android Studio und erzeugen ein neues Projekt namens HeiseNatScrLibrary.

Nach dem Rechtsklick in das Project-Feld und der anschließenden Auswahl von Option New | Module erscheint der Create New Module-Assistent zum Anlegen eines neuen Projekts auf Basis der Vorlage Android Library. Abbildung 10 zeigt die resultierende Projektstruktur.

Applikations- und Bibliotheksprojekt sind am Platz (Abb. 10).
Applikations- und Bibliotheksprojekt sind am Platz (Abb. 10).

Der nächste Schritt ergänzt das Hauptpaket des Applikationsprojekts um eine .java-Datei. Für das Beispiel reicht folgender Inhalt:

package com.tamoggemon.myheiselibrary;
public class NumbersTestClass
{
static public void doSomething(float f)
{
Log.e("Heise", "Float ist eingetroffen");
}
static public void doSomething(int i)
{
Log.e("Heise", "Int ist eingetroffen");
}
}

Ein Klick auf Build | Rebuild Project weist Android Studio dazu an, die Bibliothek neu zu generieren. Ein Blick auf das Release-Verzeichnis zeigt nur eine .aar-Datei. Die von Android Studio beim Kompilieren erzeugte .jar-Bibliothek liegt im Verzeichnis HeiseNatScrLibrary/myheiselibrary/build/intermediates/bundles/debug und heißt immer classes.jar.

Älteren Versionen von NativeScript haben das Archiv ins Projektstammverzeichnis kopiert. Das Einbinden erfolgte durch Eingabe von tns library add. Seit Version 1.6 quittiert die Runtime das Vorgehen mit folgender Fehlermeldung:

~/nativespace/HeiseSample$ \
tns library add android classes.jar
Unknown command 'library'. Use 'tns help' for help.
...

Damit möchte Telerik die Nutzung des neu geschaffenen Plug-in-Formats erzwingen. Im Internet finden sich immer wieder Verweise auf die URL http://docs.nativescript.org/runtimes/android/external-libs/jars. Die dortigen Informationen lassen sich wegen des weggefallenen library-Kommandos nicht mehr verwenden, und folgerichtig ist der Link inzwischen nicht mehr erreichbar.

Entwickler erzeugen zum Verwenden des neuen Formats ein Verzeichnis im NativeScript-Arbeitsverzeichnis, und errichten folgende Ordnerstruktur:

|---index.js
|---package.json
|---platforms
|---android
|---AndroidManifest.xml
|---libs
|---classes.jar


NativeScript-Plug-ins sind im Grunde genommen npm-Pakete, die einige spezifische Erweiterungen aufweisen. Die Datei package.json muss für das vergleichsweise simple Projekt folgendermaßen aussehen:

{
"name": "heisesample",
"version": "0.0.1",
"main": "index.js",
"nativescript": {
"platforms": {
"android": "2.0.0"
}
}
}

Plug-ins sind zum Teilen vorgesehen: Die Nutzer der Pakete sollten sich so wenig wie möglich mit dem unterliegenden System auseinandersetzen müssen. Die in der Manifestdatei befindlichen Informationen wandern beim Kompilieren ins Hauptmanifest. Wer die Berechtigung zur Nutzung des Internet anfordern möchte, muss die Datei mit folgendem Inhalt ausstatten:

<?xml version="1.0" encoding="UTF-8"?>
<manifest xmlns:android=
"http://schemas.android.com/apk/res/android">
<uses-permission android:name=
"android.permission.INTERNET" />
</manifest>

In index.js könnten Entwickler Code platzieren, der die in der nativen Bibliothek für JavaScript-Entwickler exponiert. Es handelt sich dabei um eine Art Interface, das die internen Methoden nach außen trägt.

Da das Beispiel auf die nativen Routinen direkt zugreift, darf die Datei leer bleiben. Der Befehl zum Einbinden des Plug-ins in den Kompilierprozess sieht folgendermaßen aus:

~/nativespace/HeiseSample$ \  
tns plugin add "/home/tamhan/nativespace/HeisePlugin"
heisesample@0.0.1 node_modules/heisesample
Successfully installed plugin heisesample.

Da kein Wrapper-Code existiert, muss der Zugriff auf die Klassen von Hand erfolgen. Dazu genügt zunächst folgende Anpassung in main-page.js:

var createViewModel = 
require("./main-view-model").createViewModel;
function onNavigatingTo(args) {
var page = args.object;
page.bindingContext = createViewModel();
var instanz=com.tamoggemon.myheiselibrary.NumbersTestClass;
instanz.doSomething(22);
instanz.doSomething(22.2);
}
exports.onNavigatingTo = onNavigatingTo;

Theoretisch sollte das Eingeben von tns run android eine aktualisierte Version des Programms auf das Smartphone hochladen. Leider funktioniert das Vorgehen noch nicht – dank diverser Caches in Gradle kommt es in der Praxis meist zu Fehlern folgender Art:

:buildMetadata
Exception in thread "main" \ 
java.lang.IllegalArgumentException: \
Class com.tns.internal.AppBuilderCallback conflict: \
/home/tamhan/nativespace/HeiseSample/platforms/\
android/build/intermediates/classes/F0F1/debug and \
/home/tamhan/nativespace/HeiseSample/platforms/\
android/build/intermediates/classes/F0/debug
...
:buildMetadata FAILED

Da tns derzeit kein clean-Kommando enthält [7], muss an dieser Stelle gradlew helfen:

~/nativespace/HeiseSample$ \
cd platforms/android/
~/nativespace/HeiseSample/\
platforms/android$ ./gradlew clean

Nach dem Anstoßen von tns run android
erscheint im LogCat-Fenster von Android
Studio folgendes:

06-21 00:06:39.030 \
18994-18994/org.nativescript.HeiseSample \
E/Heise: Int ist eingetroffen
06-21 00:06:39.030 \ 
18994-18994/org.nativescript.HeiseSample \
E/Heise: Float ist eingetroffen

Die Unterscheidung zwischen float und int wirkt auf den ersten Blick ungewöhnlich, da JavaScript nur den Number-Typ kennt. Von Haus aus unterteilt NativeScript nach einem einfachen Schema: Gibt es eine Nachkommastelle, ist es ein float. Zum sicheren Ansprechen numerischer Overloads bietet NativeScript vier Mnemonics an, die Zahlen gezielt in ihre Java-Äquivalente umwandeln:

myObject.myMethod(byte(10));
myObject.myMethod(short(10));
myObject.myMethod(float(10));
myObject.myMethod(long(10));

Fertige Plug-ins

Ein altes Sprichwort besagt, dass vermiedene Arbeit doppelt effizient ist. Wer sein Projekt um eine oder mehrere native APIs ergänzen möchte, sollte vor dem Erzeugen eines eigenen Plug-ins einen Blick auf das Verzeichnis [8] werfen.

Fazit

NativeScript und React Native zeigen, dass verschiedene Wege nach Rom führen. Teleriks Produkt ist dem Angebot aus dem Hause Facebook insofern überlegen, als die XML-basierte GUI-Engine das Erzeugen von Cross-Plattform-Applikationen wesentlich erleichtert.

Auf der Sollseite steht derweil das Metadatensystem, das das Einbinden von Codemodulen erschwert. Während Entwickler bei React Native ihre native Logik beliebig zusammenstellen und mit einer komfortablen API bei der Runtime anmelden, müssen sie in NativeScript eine vergleichsweise umfangreiche Prozedur hinter sich bringen.

Der Vergleich der beiden Produkte fällt schwer: Wer viel zwischen nativem und webbasiertem Code hin- und herwechselt und schon nativen Code hat, wird mit NativeScript nicht sonderlich glücklich. Entwickler, die ein Projekt von Grund auf starten und eine Anwendung mit wenig Schnittstellen zwischen nativem und lokalem Code realisieren, dürften NativeScript ob des Vorhandenseins eines Cross-Platform-GUI-Stacks bevorzugen. (rme [9])


Tam Hanna
befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handheldcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenews-Dienste zum Thema und steht für Fragen, Trainings und Vorträge gern zur Verfügung.


URL dieses Artikels:
http://www.heise.de/-3282619

Links in diesem Artikel:
[1] https://www.heise.de/developer/artikel/Native-Android-und-iOS-Apps-mit-React-Native-erstellen-3025889.html
[2] https://www.nativescript.org/blog/details/nativescript-runtime-preview-for-windows-10
[3] http://docs.nativescript.org/ui/styling
[4] https://docs.nativescript.org/runtimes/android/marshalling/js-to-java.html
[5] https://docs.nativescript.org/runtimes/android/marshalling/java-to-js.html
[6] https://docs.nativescript.org/plugins/ui-plugin
[7] https://github.com/NativeScript/android-runtime/issues/430
[8] https://plugins.telerik.com/nativescript
[9] mailto:rme@ct.de