Mit JavaScript wie hausgemacht: NativeScript

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

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


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.