NativeScript

von Johannes Hoppe und Sebastian Witalec | Screenguide #31, Technologie

Statt eigenständige, native Apps für die mobilen Betriebssysteme zu erstellen, können Sie auf hybride Apps auf Basis von HTML und JavaScript setzen. Dabei sind Beschränkungen schwer zu vermeiden. Das Open-Source-Framework NativeScript schickt sich an, die letzten vorhandenen Grenzen einzureißen: echte native Apps auf Basis von JavaScript.

Hände von drei Menschen, die auf ihre Smartphones schauen 

Die Anforderungen an moderne Apps sind unter anderem eine ansprechende Ästhetik, ein plattformspezifisches Nutzererlebnis und natürlich bestmögliche Performance. Normalerweise werden hierzu eigenständige Apps für die beiden großen mobilen Betriebssysteme erstellt. Doch parallele Entwicklungen erzeugen gleichzeitig erhöhte Kosten. Eine Antwort darauf sind hybride Apps auf Basis von HTML und JavaScript. Das bedeutet automatisch aber ein paar technische Beschränkungen. Durch NativeScript von Telerik (ein Unternehmen von Progress) ist es möglich, direkt mit JavaScript native Apps zu entwickeln. Diese Apps sind nicht mehr von Lösungen unterscheidbar, die klassisch auf Basis von Objective-C bzw. Swift oder Java entwickelt worden sind. Sie verfügen über eine beachtliche Geschwindigkeit und verwenden die normalen Bedienelemente des jeweiligen mobilen Betriebssystems.

Zwei NativeScript-Screens unter iOS
Abb. 1: Zwei NativeScript-Screens unter iOS

Was ist NativeScript?

NativeScript ist ein Open-Source-Framework zur Entwicklung von mobilen Apps. Neben der Programmiersprache JavaScript wird auch die JavaScript-Obermenge TypeScript direkt unterstützt. Aktuell stehen als Zielplattform sowohl Android als auch iOS zur Verfügung. Seit der jüngsten Version 1.7 (März 2016) ist auch ein Support für universelle Windows-Plattform- Apps (UWP) hinzugekommen. Jene universellen Apps sind auf Windows Phone 10 und Windows 10 ausführbar. Momentan wird die neueste Zielplattform aber noch als „proof of concept” eingestuft. Zum aktuellen Zeitpunkt sind nur NativeScript-Apps für Android und iOS reif für den produktiven Einsatz.

Auf den ersten, flüchtigen Blick scheint das Framework eine weitere Variante des hybriden Ansatzes zu sein. Das bekannteste hybride Framework dürfte Apache Cordova (ehemals PhoneGap genannt) sein, das den Einsatz von HTML und JavaScript zur App-Entwicklung salonfähig gemacht hat. Cordova lässt Webanwendungen in einem Browser laufen und ermöglicht über Schnittstellen ebenso den Zugriff auf native Funktionen. Sofern Sie mit dem SPA-(Single Page Applications-)Framework AngularJS vertraut sind, steht Ihnen z.B. durch Einsatz des Ionic- Frameworks eine populäre und weitverbreitete Lösung zur Verfügung. Durch den Einsatz von vorbereiteten Themes und eigenem grafischen Geschick muten die fertigen Apps anschließend wie native Anwendungen an. Ein grundlegendes Problem ergibt sich jedoch prinzipiell immer: Eine hybride App ist und bleibt eine Website, die nur den Anschein erweckt, es handle sich um eine native Anwendung.

Die verwendeten JavaScript Virtual Machines
Abb. 2: Die verwendeten JavaScript Virtual Machines

Im Gegensatz dazu reiht sich NativeScript in eine neue Disziplin ein. In dieser Disziplin geht es darum, JavaScript als vollwertige Programmiersprache für Apps zu etablieren. Weitere Frameworks, die native Apps mit JavaScript ermöglichen, sind React Native von Facebook und Appcelerator Titanium. Bei allen drei Lösungen fällt der Umweg über HTML und das DOM schlicht weg. Die Frameworks ermöglichen die direkte Verwendung von nativen UI-Elementen aus der JavaScript-Umgebung heraus. Bei NativeScript für Android ist diese Umgebung Googles V8- Engine. Unter iOS sowie unter Windows kommt JavaScriptCore zum Einsatz.

Warum NativeScript?

Die technische Grundlage mag zwar spannend sein, doch im Projektalltag zählen praktische Gründe. Eine Reihe von Gegebenheiten spricht für den Einsatz von NativeScript.

Wiederverwendung von bestehenden Skills: Das Erlernen einer neuen Programmiersprache zum Zwecke der App-Entwicklung ist anstrengend und aufwendig. Der Erwerb von Grundlagen einer Programmiersprache ist noch das kleinere Problem. Der eigentliche Aufwand liegt im Detail. Es ist ein mühsamer und intensiver Prozess, bis ein Neueinsteiger tatsächlich alle Aspekte einer Programmierwelt kennt und sicher beherrschen kann. Während dieser Einarbeitung steht der Programmierer natürlich nicht mehr mit dem gewohnten Potenzial und der üblichen Kapazität zur Verfügung.

Wenn Sie hingegen schon einmal eine Anwendung für das Web entwickelt haben, so sind sie unweigerlich mit JavaScript, gegebenenfalls sogar mit TypeScript und auf jeden Fall mit CSS in Berührung gekommen. Damit steht Ihnen bereits ein großer Teil des notwendigen Wissens zur Verfügung. Das dedizierte Erlernen von Objective-C, Swift, Java und/oder C# entfällt. Sollten Sie sich zudem mit den neuesten Trends zur Webentwicklung beschäftigen, dann wird für Sie die Unterstützung von Angular 2.0 von großem Interesse sein. Auf diese spannende Allianz von NativeScript und AngularJS werden wir im zweiten Teil dieses Artikels noch intensiver eingehen.

Wiederverwendung von bestehendem Code: Durch den Einsatz der Programmiersprache JavaScript bietet es sich an, bestehende Geschäftslogik oder Bibliotheken aus dem Internet weiterzuverwenden. Mit dem Repository NPM steht ein großer Fundus von kurzen Schnipseln bis hin zu ganzen Bibliotheken zur Auswahl. Es ist lediglich zu beachten, dass in NativeScript kein DOM existiert. Das ist aber prinzipiell kein Problem, denn diese Restriktion gilt auch für alle üblichen Node.js-Pakete. Wenn Sie z.B. ein Datum formatieren wollen, dann können Sie dafür die bekannte Bibliothek „moment” nutzen. Nach einer Installation per „npm install moment” steht Ihnen die Funktionalität wie üblich zu Verfügung:

JavaScript
  1. // Verwendung eines NPM-Pakets
  2. var moment = require("moment");
  3. var formattedTime = new moment().
  4. format("HH:mm:ss");

Es ist weiterhin möglich, bestehende native Fremdbibliotheken für Android und iOS anzusprechen. Das bedeutet, dass Sie nicht in der JavaScript- bzw. NativeScript-Welt gefangen sind. Wenn es notwendig ist, können Sie auch sehr plattformspezifischen Code aufrufen. Von diesem Prinzip macht auch die Komponenten-Sammlung „UI for NativeScript” Gebrauch. Die bestehenden Komponenten-Sammlungen sind hier vom Hersteller mit einem JavaScript-Wrapper vereinheitlicht worden.

Direkter Zugriff auf native APIs: Manchmal werden Sie einfach nicht drumherum kommen und Sie müssen doch tief in das Betriebssystem abtauchen. Für diese Fälle bietet NativeScript den direkten Zugriff auf native APIs aus JavaScript heraus an. Das ist ein großer Vorteil gegenüber React Native und Appcelerator, wo dies nicht so einfach möglich ist. Diesen Aspekt werden wir gleich noch einmal näher beleuchten.

Open Source: Die Gretchenfrage in Sachen Software lässt sich bei NativeScript schlicht beantworten. Ja, es ist Open-Source. Es steht unter der „Apache License, Version 2.0” (ASLv2), welche die Kombination mit proprietärem Code erlaubt. Das NativeScript-Team selbst besteht aus rund 35 Köpfen. Das Team wiederum wird von einer aktiven Community unterstützt, die auf Github die Entwicklung über Bug-Reports und Pull-Requests antreibt. Es ist problemlos möglich, einen kompletten NativeScript-Workflow mittels der „NativeScipt-CLI” aufzubauen. Wenn Sie es etwas komfortabler wünschen oder kommerziellen Support benötigen, gibt es von Progress entsprechende kostenpflichtige Angebote. Lizenzpflichtig sind ebenso die erweiterte Komponentensammlung „UI for NativeScript Pro” und die integrierte Entwicklungsumgebung „Telerik Plattform”.

Hinter den Kulissen

Wie ist es möglich, unter Verwendung einer einzigen Code-Basis mehrere Plattformen anzusprechen? Die Grundlage hierfür bietet das NPM-Paket „Telerik NativeScript Core Modules” (kurz: „tns-core-modules”). Die darin enthaltenen Module bilden eine Abstraktionsschicht, die spezifische Implantierungen für die unterstützten Plattformen enthält. Hier finden sich Module für die unterschiedlichen Aspekte der mobilen Entwicklung, von UI-Abstraktion über Gerätesensoren bis hin zum Hardware-Zugriff. Da die NativeScript-Module in TypeScript geschrieben sind, bieten kompatible Entwicklungsumgebungen dank der Typ-Definition eine komfortable automatische Code-Vervollständigung und auch Syntax-Prüfungen an. Eigene Module können Sie im selben Stil erstellen und somit das Software-Ökosystem erweitern.

Die Abstraktionsschicht in einer Übersicht
Abb. 3: Die Abstraktionsschicht in einer Übersicht

UI-Abstraktion: Die Gestaltung von Oberflächen wird natürlich nicht durch eine Aneinanderreihung von JavaScript-Befehlen realisiert. Stattdessen definieren Sie die Oberfläche in einem spezifischen XML-Dialekt. Tags wie „Button”, „TextField”, „Date-Picker” werden hierbei in die jeweiligen Oberflächenelemente umgewandelt:

XML
  1. <!-- Einfache Seite mit Text und Button -->
  2. <Page>
  3.   <StackLayout>
  4.     <Label text="Name"></Label>
  5.     <TextField text="{{nameAttribute}}">
  6.     </TextField>
  7.     <Button text="Press Me" tap="doSomething">
  8.     </Button>
  9.   </StackLayout>
  10. </Page>

Beim „Build” der Anwendung wird jeder Tag durch das jeweilige native Äquivalent ersetzt. So wird etwa aus dem „TextField” je nach Plattform „android.widget.EditText” (bei Android) bzw. „UITextField” (bei iOS).

Plattformspezifischer Code

Es gibt Situationen, in denen der Aufruf von plattformspezifischem Code notwendig wird. Das kann zum Beispiel der Fall sein, wenn eine Funktionalität tatsächlich nur auf der jeweiligen Plattform existiert, wenn eine native Fremdbibliothek eingebunden werden soll oder das gewünschte Feature tatsächlich einfach noch nicht über ein Core-Modul implementiert wurde. Hier kommt der Zugriff auf die „Native API” ins Spiel. Die native Welt kann dabei so aufgerufen werden, als ob es sich um normale JavaScript-Methoden handeln würde.

Als Beispiel soll das Datum der letzten Modifikation einer Datei ermittelt werden. Diese Funktionalität wird für Android und iOS gänzlich anders umgesetzt.

JavaScript
  1. // Zugriff auf das Datum der letzten Modifikation unter iOS
  2. var fileManager = NSFileManager.defaultManager();
  3. var attributes = fileManager.attributesOfItemAtPathError(path);
  4. var lastModifiedDate = attributes
  5. objectForKey(this.keyModificationTime);
JavaScript
  1. // Zugriff auf das Datum der letzten Modifikation unter Android
  2. var javaFile = new java.io.File(path);
  3. var lastModifiedDate = new Date(javaFile.
  4. lastModified());

Das Beste an der gezeigten Syntax ist die Tatsache, dass sowohl Namespaces als auch Attribute und Typen sowie die gesamten Konventionen bei der Benennung dem Pendant aus der Android- bzw. iOS-Dokumentation entsprechen. Dasselbe gilt für Fremdbibliotheken. So bringen Sie mit geringem Aufwand ein Code-Fragment aus den Dokumentationen oder dem Netz per Copy-and-Paste zum Laufen. Hinter den Kulissen verwendet NativeScript „Reflection”, um eine Liste von APIs aufzubauen, die auf der aktuellen Plattform zur Verfügung stehen und zum globalen Gültigkeitsbereich hinzugefügt werden. Die Details zu der verwendeten Technik können sie in einem detaillierten Artikel nachvollziehen .

Styling

Bei der Gestaltung von Webseiten trennt man Struktur (HTML) und Design (CSS). Das direkte Styling von Elementen würde viel zu unübersichtlich sein und zu redundanten Deklarationen führen. Ganz ähnlich verhält es sich bei der Gestaltung einer NativeScript-App. Das Framework folgt hierbei den Spezifikationen von CSS und verwendet die bekannte Syntax, das Prinzip der kaskadierten Regeln und eine Auswahl an Selektoren und Deklarationen.

Mittels CSS lassen sich einfache Dinge wie Hintergrundfarbe und Schriftgröße bis hin zu komplexen Animationen realisieren.

CSS
  1. /* Anpassung von Hintergrundfarbe und Schrift größe per CSS */
  2. .small-label {
  3.   font-size: 20;
  4.   color: #284848;
  5.   horizontal-align: center;
  6. }
CSS
  1. /* Einen Button per CSS animieren */
  2. .button1:highlighted {
  3.   animation-duration: 1s;
  4.   animation-name: test;
  5. }
  6. @keyframes test {
  7.   from { transform: none; }
  8.   20% { transform: rotate(45); }
  9.   50% { transform: rotate(50) scale(1.2, 1.2) translate(50, 0); }
  10.   100% { transform: rotate(0) scale(1.5, 1.5) translate(100, 0); }
  11. }

Das Styling per CSS ist bei NativeScript im Vergleich zum Browser jedoch sehr limitiert. Es handelt sich hierbei um eine übersichtliche Teilmenge des bekannten Browser-CSS. Dies liegt nicht zuletzt auch darin begründet, dass die verfügbaren Eigenschaften dem gemeinsamen Nenner der drei verfügbaren Plattformen entsprechen müssen.

Angular 2

NativeScript wurde als reines JavaScript-Framework geschaffen, das User-Interfaces per XML definiert und mit CSS formatiert. Mit Version 1.0 startete im Mai 2015 die erste Ausgabe für den produktiven Einsatz. Etwa zur selben Zeit nahm auch die Entwicklung der zweiten Version von AngularJS ordentlich Fahrt auf. Angular 2 wurde als komplette Neuentwicklung konzipiert. Im Gegensatz zum Vorgänger sind Sie nicht mehr nur auf den Browser festgelegt. Die Architektur von Angular ist darauf ausgelegt, vollkommen unabhängig von der eingesetzten Umgebung zu sein. Dies ermöglicht es, mit Angular 2 sowohl für Web als auch Desktop und Mobile zu entwickeln. Die Synergien aus beiden Projekten sind offenkundig, und so arbeiten die Teams von Telerik bzw. Progress und Google seit nun fast einem Jahr zusammen, um eine nahtlose und stabile Integration zu schaffen. Dabei ist es eine gute Fügung, dass beide Projekte auf TypeScript setzen.

NativeScript loves Angular
Abb. 4: NativeScript loves Angular

Als kleines Beispiel setzen wir einen Login-Screen mit Angular 2 um. Sollten Sie noch nie mit Angular 2 in Berührung gekommen sein, verschafft Ihnen der offizielle „5-Minuten-Schnellstart” einen guten Überblick.

Zunächst benötigen wir eine Klasse mit dem Namen „UserService”, der die Logik für die Anmeldung beinhaltet und die Zugangsdaten zum Backend senden soll:

TypeScript
  1. // Der UserService kapselt die eigentliche Login-Funktionalität
  2.  
  3. import {Injectable} from "angular2/core";
  4.  
  5. @Injectable()
  6. export class UserService {
  7.   login(userName: String, password: String) {
  8.     return doSomeMagicHereAndReturnPromise();
  9.   }
  10. }

Die wichtigsten Grundbausteine in Angular 2 sind Komponenten. Komponenten definieren ganze Seiten, einzelne UI-Elemente und Routen. Eine Komponente hat immer ein Template, das aber im Falle von NativeScript kein normales HTML beinhaltet. Angular 2 sorgt auch gleich noch per „Dependendy-Injection” (DI) für die Verdrahtung von UserService und Komponente, sodass wir uns um die Initialisierung des UserService nicht zu kümmern brauchen:

TypeScript
  1. // Die Komponente LoginPage verknüpft die Geschäftslogik mit dem UI
  2.  
  3. import {UserService} from "./user.service";
  4.  
  5. @Component({
  6.   selector: "my-app",
  7.   providers: [UserService],
  8.   templateUrl: "pages/login/login.html"
  9. })
  10. export class LoginPage {
  11.   userName: String;
  12.   password: String;
  13.     constructor(private _userService: UserService)
  14.   {
  15.   this.username = "user@example.org";
  16.   this.password = "password";
  17.   }
  18.   login() {
  19.     this._userService.login(this.username, this.password)
  20.     .subscribe(
  21.       () => doSomethingOnSuccessfulLogin (),
  22.       (error) => alert("Unfortunately we could not find your account.")
  23.     );
  24.   }
  25. }

Die Komponente „LoginPage” agiert als Bindeglied zwischen UserService und dem Template. Auf die Methode „login” sowie auf die beiden Eigenschaften „username” und „passwort” können wir nun im Template zugreifen.

Zum Schluss müssen wir nur noch einen Screen im Template definieren. Der bereits vorgestellte XML-Dialekt zur Definition der Oberfläche kommt auch hier zum Einsatz. Die [eckigen] und (runden) Klammern sind der Template-Syntax von Angular 2 geschuldet. Es handelt sich um sogenannte „Bindings”, die als Verknüpfung mit der Komponente dienen. Da die Verwendung dieser Klammern kein valides XML ergeben würde, deklarieren wir das Markup lieber als HTML statt als XML.

Zum Schluss müssen wir nur noch einen Screen im Template definieren. Der bereits vorgestellte XML-Dialekt zur Definition der Oberfläche kommt auch hier zum Einsatz. Die [eckigen] und (runden) Klammern sind der Template-Syntax von Angular 2 geschuldet. Es handelt sich um sogenannte „Bindings”, die als Verknüpfung mit der Komponente dienen. Da die Verwendung dieser Klammern kein valides XML ergeben würde, deklarieren wir das Markup lieber als HTML statt als XML.

HTML
  1. <!-- Die Komponente LoginPage verknüpft die
  2. Geschäftslogik mit dem UI -->
  3. <StackLayout>
  4.   <Image src="res://logo_login" stretch="none" horizontalAlignment="center">
  5.   </Image>
  6.   <TextField hint="Email"
  7.    [(ngModel)]="username">
  8.   </TextField>
  9.   <TextField hint="Password" secure="true"
  10.    [(ngModel)]="password">
  11.   </TextField>
  12.   <Button [text]="Sign up" (tap)="login()">
  13.   </Button>
  14.   <Button [text]="Back to login">
  15.   </Button>
  16. </StackLayout>

Zusammen mit etwas zusätzlichem Styling ergibt sich ein fertiger Dialog. Et voilà! Schon steht die erste Seite unserer Anwendung. Das hier gezeigte Beispiel ist ein angepasster Ausschnitt aus der offiziellen Demo-App „Groceries”, mit der Sie eine Einkaufsliste zusammenstellen und teilen können. Die App kann einmal mit purem NativeScript und einmal in Kombination mit Angular 2 nachprogrammiert werden. Hier wird auch die Installation der notwendigen Abhängigkeiten – vor allem das iOS SDK und das Android SDK – detailliert beschrieben.

Die finale Login-Seite unter Android und iOS
Abb. 5: Die finale Login-Seite unter Android und iOS

Fazit

Der Einstieg in die App-Entwicklung mit NativeScript ist für einen Webentwickler nicht sonderlich schwer. Angular 2 gibt der Anwendung eine solide Architektur vor und sorgt dafür, dass Geschäftslogik und Oberflächenelemente sauber getrennt bleiben. NativeScript wiederum liefert die Mechanismen, um mit nativen APIs und nativen UI-Elementen zu interagieren. Sie erhalten das Beste aus allen Welten: JavaScript bzw. TypeScript als Programmiersprache, Angular 2 als Oberflächenframework und das native Look-and-Feel, das schlussendlich dem Anwender gefallen wird.

Johannes Hoppe 

Johannes Hoppe

ist selbständiger IT-Berater, Softwareentwickler und Trainer für .NET und AngularJS. Er ist Leiter der .NET User Group Rhein-Neckar und unterrichtet als Lehrbeauftragter der DHBW Mosbach. Johannes schreibt über seine Trainings und Vorträge in seinem Blog [blog.johanneshoppe.de]. Demnächst wird sein Buch zu Angular 2 beim dpunkt.verlag erscheinen.

Twitter: @JohannesHoppe

Sebastian Witalec 

Sebastian Witalec

ist Technical Evangelist bei Progress und hat über acht Jahre Erfahrung in der Softwareentwicklung. Sein Fokus liegt seit einigen Jahren auf der Cross-Plattform-Mobile-Entwicklung. Dadurch lernte er Apache Cordova und NativeScript kennen. Er lebt in London und arbeitet dort eng mit verschiedenen Entwicklercommunitys zusammen.

Twitter: @sebawita

Neuen Kommentar schreiben