Neuigkeiten von trion.
Immer gut informiert.

Mobile Anwendungsentwicklung im Enterprise-Umfeld: NativeScript

NativeScript logo

Mobile Entwicklung für Enterprise Anwendungen
Bei der mobilen App-Entwicklung gibt es heutzutage viele verschiedene Optionen. Im letzten Artikel [3] wurde bereits React-Native als ein Weg zum Ziel beschrieben. Dort wurde die insbesondere für Enterprise-Anwendungen größtenteils fehlende MVC-Trennung angemerkt. Eine weitere Alternative mit anders umgesetzter Separation of Concerns ist NativeScript [1].

In diesem Artikel wollen wir eine Basis für App-Entwicklung aller Art mit NativeScript legen, daher ist der gesamte Quellcode auf GitHub verfügbar [2]. Er kann direkt ausgechecked und auf die eigenen Bedürfnisse ausgebaut werden. Das spart Zeit beim initialen Setup.

NativeScript

Im letzten Artikel [3] haben wir das Universum der mobilen Entwicklung bereits in Progressive Web Apps und native Anwendungen unterteilt. Während Progressive Web Apps im Browser laufen, werden native Apps direkt auf dem System gestartet. Dadurch können sie auf erweiterte Plattformfunktionen wie Push-Benachrichtigungen und Standortinformationen zugreifen.

Bei den nativen Anwendungen stellt sich die Frage, ob die eigene Anwendung direkt für die entsprechenden Plattformen mit den dazugehörigen spezifischen Tools entwickelt werden soll. Bei iOS-Apps wäre das heutzutage XCode und Swift, bei Android-Apps meist Java und Android Studio. Dies erfordert zwei Teams, welche zwei komplett separate Anwendungen entwickeln und warten müssen. Im Optimalfall sollten zusätzlich beide Anwendungen synchron gehalten werden, was zu Verzögerungen im Featureauslieferungsprozess führen kann. Es kann vorkommen, dass ein Feature, welches auf einer Plattform trivial zu implementieren ist, auf der anderen dann doch nicht ganz so einfach umsetzbar ist. Dann muss unter Umständen das erste Team einige Zeit warten bis das neue Feature auch auf der zweiten Plattform funktioniert.

Als Lösung zu diesem und weiteren Problemen bieten sich die Cross-Platform Frameworks an. Wie auch React Native ist NativeScript ein derartiges Cross-Platform Framework für native Mobilanwendungen.

001 application hierarchy
Figure 1. Einordnung von NativeScript in die Hierarchie mobiler Anwendungen.

Cross-Platform-Framworks haben den Vorteil, dass aus einer einzelnen Codebasis native Anwendungen für die unterschiedlichen mobilen Betriebssysteme generiert werden können. Daher müssen Entwickler für die meisten Anwendungen kein allzu tiefes konkretes Plattformwissen besitzen, was den Entwicklungsaufwand und die Time to Market reduziert. Der gesamte Quellcode muss nur einmal entwickelt werden und wird automatisiert auf beide Plattformen transpiliert. Dadurch kann außer in Ausnahmefällen sichergestellt werden, dass ein neues Feature gleich auf beiden Plattformen funktioniert.

Wie schon angesprochen gibt es neben NativeScript auch andere Frameworks wie React Native, dem wir bereits einen eigenen Artikel gewidmet haben [3]. Im Kontrast zu React Native sticht bei NativeScript insbesondere die gute Integrierbarkeit mit Web-Frameworks wie Angular und Vue.js ins Auge. Das tröstet auch gleich über die etwas kleinere Gefolgschaft von NativeScript insbesondere im Vergleich zu React Native hinweg. Auch eine vernünftig funktionierende MVC-Trennung sehen wir als Vorteil im Bezug auf die Entwicklung von Enterprise-Anwendungen.

Da auch NativeScript TypeScipt-Unterstützung von Haus aus mitbringen, möchten wir dieses auch gleich statt JavaScript für unseren kleinen Prototypen verwenden.

Verschwenden wir nun keine weitere Zeit, sondern fangen gleich an!

Setup der Entwicklungsumgebung und erste Anwendung

Wir gehen hier davon aus, dass eine entwicklungsfähige Android-Umgebung mit korrekt gesetzten Umgebungsvariablen bereits vorliegt. Das Setup einer Android-Entwicklungsumgebung würde für diesen Artikel den Rahmen sprengen. Sofern es beim Durchführen der Setupschritte für die NativeScript-Anwendung zu Problemen kommt, hilft der Befehl tns doctor, welcher die Umgebung prüft und detailliert anzeigt, ob und wo ein Problem vorliegen könnte. Hierbei werden insbesondere die zum Betrieb von NativeScript notwendigen Teile des Android Platform SDK sowie Emulatorunterstützung geprüft.

Als Emulator für ein Android-Smartphone verwenden wir für diesen Artikel erneut die virtuellen Geräte vom AVD-Manager in Android Studio [6].

  1. Für die lokale Entwicklung mit NativeScript und Typescript wird zunächst einmal NodeJS und NPM benötigt. NodeJS kann auf der offiziellen Seite [5] als Archiv heruntergeladen werden. Nach dem Herunterladen sollte der bin-Ordner zum PATH oder zu den Benutzerumgebungsvariablen (Windows) hinzugefügt werden.

  2. Für die Initialisierung einer neuen Projekt-App wird das NativeScript Command Line Interface benötigt. Es kann via Node mit dem Befehl npm install -g nativescript installiert werden.

  3. Nun kann ein neues Projekt mittels npx tns create <app-name> --template typescript initialisiert werden. Dadurch wird ein neues App-Projekt mit einfacher Konfiguration und einer Beispielseite sowie Typescriptunterstützung angelegt.

  4. Als nächstes sollte mit tns devices geprüft werden, ob der Emulator in der Geräteliste auftaucht. Sofern dies nicht der Fall hilft es, ihn einmalig zu starten. Sobald ein Emulator einmal gelinked ist, kann er mit dem Befehl in Schritt fünf gestartet werden.

  5. Mit dem Befehl tns run android im Projektverzeichnis (das mit der package.json-Datei, nicht das mit der app.ts-Datei) wird die Anwendung auf dem Emulator gestartet. Wer ein passendes Smartphone zur Hand hat kann stattdessen auch den Befehl tns preview verwenden und den daraufhin in der Konsole auftauchenden QR-Code scannen. Dann kann über die beiden Apps NativeScript Playground [4] und NativeScript Preview Apps [5] direkt auf dem Smartphone getestet werden. Diese beiden Apps haben wir natürlich auch getestet und sie funktionieren bisher hervorragend. Allerdings hat vielleicht nicht jeder ein Androidgerät zur Hand, weswegen wir in diesem Artikel auf einen Emulator setzen.

Abschließend sollte sich im Emulator die Demo-App öffnen.

002 app first start
Figure 2. Android Emulator mit der brandneuen Anwendung.

Sehen wir uns mal an, wie es zu dem hübschen Button und dem Zähler in der App kommt.

Die erste NativeScript-App

An dieser Stelle sollte noch einmal darauf hingewiesen werden, dass für besonders zeitbewusste Lesende alle Codebeispiele als git-Repository verfügbar sind [2]. Dort können dann nacheinander die Commits ausgecheckt werden, um die Kapitel in dieses Tutorial Schritt für Schritt nachzuvollziehen.

Wie auch bei React Native werfen wir als erstes einen Blick auf die Highlights der erzeugten Verzeichnisstruktur.

ShowcaseApp/
├── app/: Quellcode für die Cross Platform App.
│ ├── `App_Resources/: Resourcen wie Bilder oder plattformspezifische Konfiguration (z.B. AndroidManifest.xml).
│ ├── `app.ts: Haupteinstiegspunkt der Typescript Anwendung.
│ ├── `main-page.ts: Managementcode der zur Hauptseitenlogik und oberflächenbeschreibende XML-Datei mapped.
│ ├── `main-view-model.ts: Enthält die Logik assoziiert zum Hauptseitenmodul.
│ └── `main-page.xml: Beschreibt den Oberflächenaufbau und das Styling der Hauptseite
├── node_modules/: Abhängigkeiten für dieses Projekt.
├── platforms/: Quellcode für die nativen Apps für Android und iOS.
├── package.json: Definition der Abhängigkeiten für dieses Projekt.
└── tsconfig.json: Compiler- und Linterkonfigurationen für Typescript.

Ändern wir nun im ersten Schritt den angezeigten Text und die Hintergrundfarbe.

Kosmetische Anpassungen des Prototypen

Dazu müssen wir sowohl die primäre View, als auch die Logik dahinter bearbeiten. Ein bisschen wie bei WPF. Da bei NativeScript eine strenge MVC-Trennung vorliegt, müssen dafür mehrere Dateien bearbeitet werden.

Um die Hintergrundfarbe anzupassen, werden zunächst zwei CSS-Styles in die app.css eingefügt:

.green{
    background-color: #d3fccf;
}

.dark-green{
    background-color: #819c7e;
}

Anschließend wird die Datei main-page.xml angepasst, welche das Layout definiert:

<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="green">
    <ActionBar title="My App" icon="" class="dark-green"></ActionBar>
    <StackLayout class="p-20">
        <Label text="Schaltfläche antippen" class="h1 text-center"/>
        <Button text="ANTIPPEN" tap="{{ onTap }}" class="-primary"/>
        <Label text="{{ message }}" class="h2 text-center" textWrap="true"/>
    </StackLayout>
</Page>

In dieser Datei gibt es ein Page-Objekt zu sehen, welches eine ActionBar und ein StackLayout mit zwei Labels und einem Button dazwischen definiert.

Hier wurde im Vergleich zur Orginaldatei auf dem Page-Objekt auf oberster Ebene eine CSS-Klassendefinition class="green" eingefügt. Außerdem wurde auf dem ActionBar-Element die zweite CSS-Klasse dark-green eingefügt.

Um den statischen Text anzupassen, wurde in Zeile 4 und 5 jeweils die text-Eigenschaft der Label- und Button-Elemente geändert.

Als letztes soll noch der dynamische Text angepasst werden. Dazu müssen wir an die Business Logik der Anwendung. Diese ist in der Datei main-view-model.ts zu finden.

import { Observable } from "tns-core-modules/data/observable";

export class HelloWorldModel extends Observable {
    private _counter: number;
    private _message: string;
    constructor() {
        super();
        this._counter = 42;
        this.updateMessage();
    }
    ...
    private updateMessage() {
        if (this._counter <= 0) {
            this.message = "Hoorraaay! You unlocked the NativeScript clicker achievement!";
        } else {
            this.message = `${this._counter} taps left`;
        }
    }
}

Dort sehen wir einen Import Observable und eine Klassendefinition mit Konstruktor und den zwei privaten Variablen _counter und message. Um auch den dynamischen Text auf Deutch zu übersetzen, wird im Listing in Zeilen 14 und 16 die message übersetzt:

this.message = "Hoorraaay! You unlocked the NativeScript clicker achievement!"; 🡆 this.message = "Yuy! NativeScript-Klicker Errungenschaft freigeschaltet

sowie

this.message = ${this._counter} taps left; 🡆 this.message = Noch ${this._counter} Mal antippen;

Das Resultat sieht wie folgt aus:

003 app first changed
Figure 3. Anwendung mit angepasster Hintergrundgestaltung und neuem Text.

Der Hintergrund ist jetzt grün und der Text auf Deutsch.

Anzeige dynamisch aktualisieren mittels Observables

Wer genau hingesehen hat, dem ist aufgefallen, dass wir oben im Snippet ein Stück Code ausgelassen haben. Und zwar die Variablendefinition von _message sowie deren Getter und Setter:

get message(): string {
    return this._message;
}

set message(value: string) {
    if (this._message !== value) {
        this._message = value;
        this.notifyPropertyChange("message", value);
    }
}

Während der Getter relativ trivial ist, wird beim Setter neben einem Änderungscheck (this._message !== value) die ominöse Methode notifyPropertyChange mit dem Attribute message und dem neuen Wert aufgerufen. Hier kommt das Observer-Pattern ins Spiel. Denn diese Methode sorgt dafür, dass alle Listener, welche auf HelloWorldModel.message registriert sind, automatisch über die Änderung des Wertes informiert werden. Ohne diesen Methodenaufruf würde zwar die Variable geändert, aber sich beispielsweise der angezeigte Status in der UI nicht ändern.

Ein Beispiel für diese Listenerregistrierung ist in der Datei main-page.xml (oben gelisted) in Kombination mit der main-page.ts zu finden. In der Datei main-page.ts sehen wir nämlich, dass an die aktuelle Page eine neue Instanz des HelloWorldModel gebunden wird:

page.bindingContext = new HelloWorldModel();

Relativ weit unten in der Datei zur Oberflächenbeschreibung main-page.xml findet sich außerdem die Definition unseres Label:

<Label text="{{ message }}" class="h2 text-center" textWrap="true"/>

Das referenzierte Feld message geht hierbei auf den an das Page-Objekt gebundenen Kontext, der in der main-page.ts ja auf HelloWorldModel geht. Als Resultat wird also ein Objekt vom Typ Label erzeugt und gerendert, welches die message-Variable in unserer HelloWorldModel-Klasse observed und von der oben genannten notifyPropertyChange- Methode informiert wird.

So viel zu den Observern. Unabhängig davon wäre die bisherige Anwendung allerdings auch als Progressive Web App leicht realisierbar gewesen. Was bringt eine native Anwendung ohne native Funktionalität? Daher fügen wir nun mal eine nur für native Geräte vorbehaltene Funktion ein: GPS Geolocation.

Verarbeitung von Standortinformationen

Um Standortinformationen, etwa vom GPS-Chip eines Smartphones, aus NativeScript heraus abrufen zu können, wird zunächst das offizielle NativeScript-Plugin nativescript-geolocation benötigt [7].

Es wird mittels npx tns plugin add nativescript-geolocation, ausgeführt im Projektverzeichnis mit der package.json-Datei, zum Projekt hinzugefügt.

Öffnen wir als nächsten Schritt unsere Typescript-Klasse mit der Business-Logik, main-view-model.ts. Dort importieren wir nun ganz oben die Geolocation-Klasse des gerade eben hinzugefügten nativescript-geolcation-Plugins.

import * as Geolocation from 'nativescript-geolocation';

Anschließend passen wir die private Methode updateMessage im Body der HelloWorldModel-Klasse in der main-view-model.ts wie folgt an:

private updateMessage() {
    Geolocation.enableLocationRequest(true)
        .then(() => {
            Geolocation.isEnabled().then(locationEnabled => {
                if (locationEnabled) {
                    Geolocation.getCurrentLocation({ desiredAccuracy: Accuracy.high, maximumAge: 5000, timeout: 20000 })
                        .then(result => {
                                this.message = `${result.latitude} / ${result.longitude}`;
                        });
                }
            });
        });
}

Etwas verschachtelt.

Zunächst wird in Zeile zwei eine Standortanfrage abgeschickt. Im resultierenden Promise (Zeile 3) wird dann, sofern der Standort überhaupt aktiviert ist, ein weiterer Promise zurückgegeben (Zeile 4).

Im Kontext dieses weiteren Promise wird dann der Standort mittels GeoLocation.getCurrentLocation(…​) (Zeile 6) abgerufen.

Im daraus resultierenden, dritten Promise (Zeile 7) wird schließlich das lokale Feld message mit den empfangenen Standortinformationen aktualisiert. Die Anzeige in der App auf dem Emulator wird danach automatisch aktualisiert.

Das result beinhaltet neben Latitude und Longitude weitere Felder wie Höhe, Geschwindigkeit oder Präzision. Im Detail sind diese der Dokumentation [7] zu entnehmen.

Nach dem Speichern der Codeänderung aktualisiert sich die App und fragt gleich nach Berechtigungen für den Standortzugriff (Abbildung 4). Nach einem Klick auf die Schaltfläche wird umgehend der aktuelle Standort ausgegeben (siehe Abbildung 5).

004 app location request
Figure 4. Benutzerabfrage Standortzugriff.
005 app location fetched
Figure 5. Standortausgabe in Latitude und Longitude.

Ein weiteres häufig verwendetes Feature nativer Anwendungen sind Push-Benachrichtigungen. Da eine vollständige Firebase- oder Apple-PNS-Konfiguration den Scope dieses Artikels sprengen würde, beschränken wir uns hier auf lokale Push-Benachrichtigungen.

Push Benachrichtigungen mit NativeScript

Für die lokalen Push-Benachrichtigungen benötigen wir erneut ein Plugin. Da es hier zum aktuellen Zeitpunkt kein offizielles Plugin gibt, wird ein häufig verwendetes von Eddy Verbruggen verwendet [8]. Installiert wird es wie alle unserer Plugins mittels npx tns plugin add nativescript-local-notifications.

Neben der Importanweisung import { LocalNotifications } from "nativescript-local-notifications"; fügen wir nun eine neue Methode zum Versand von Push-Benachrichtigungen in der HelloWorldModel-Klasse in der main-view-model.ts ein:

private sendPushNotification(title: string, body: string){
    LocalNotifications.requestPermission().then(granted => {
        if(granted) {
            LocalNotifications.schedule([{
                id: 1337,
                title: title,
                body: body
            }]);
        }
    });
}

Sofern also die Berechtigung für Push-Benachrichtigungen gegeben ist (Check Zeile 2), wird eine neue Push-Benachrichtigung mittels LocalNotifications.schedule (Zeile 4ff) geplant. Sofern der Parameter at undefiniert bleibt, wird die Push-Benachrichtigung sofort versandt. Während Titel und Body der Push-Notification selbsterklärend sein sollten, identifiziert die ID eine einzelne, geplante oder angezeigte Benachrichtigung.

Mit einem erneuten Aufruf der LocalNotifications.schedule-Methode mit gleicher ID wird die bestehende Benachrichtigung aktualisiert, statt eine neue zu erzeugen. Daher bleibt es in unserem Fall bei einer, auch wenn die Schaltfläche mehrmals gedrückt wird. Nur der Popup wird erneut angezeigt. In der Liste oben bleibt es aber bei einer Notification.

Abbildung 6 unten zeigt, wie das ganze aussieht, sobald die neue Methode von der updateMessage-Methode vom letzten Promise aus aufgerufen wird (this.sendPushNotification("Standort aktualisiert", this.message);):

006 app push notification
Figure 6. Push Notification.

Mit LocalNotifications.addOnMessageReceivedCallback(…​) kann im Übrigen ein Listener hinzugefügt werden, der aufgerufen wird, sobald der Benutzer aus dem Hintergrund heraus auf die Push-Notification klickt und damit die App wieder in den Vordergrund holt.

Für unseren kleinen Prototypen haben wir nun neben einer grundlegenden UI auch native Features wie Geolocation und Push-Notifications implementiert. Und nun?

Fazit

Nun ist erst einmal aufgefallen, dass NativeScript insbesondere im Vergleich zu React Native eine gut funktionierende MVC-Trennung umsetzt und damit sehr enterprise-ready daher kommt. Auch die Entwicklung geht sehr rapide von statten. So funktioniert die Hot-Reloading-Funktionalität sowohl auf dem Emulator als auch auf der Playground App, zumindest für Android, hervorragend. Änderungen am Quellcode sind nach einmaligem Speichern direkt in der Anwendung verfügbar. Keine ewigen Wartezeiten auf den Compiler und das Uploading. Wie auch bei React Native handelt es sich bei NativeScript um eine sehr leichtgewichtige Platform, die für Geolocation und Push-Benachrichtigungen bereits externe Plugins vom selben Hersteller oder der Community benötigen. Dies hält die Kernfunktionalität schlank und reduziert unnötige Komplexität.

Aber was soll man mit einer App die die Geolocation als Push-Benachrichtigung ausgibt? Jetzt ist es Zeit, dass Sie sich ans Werk machen und auf das in diesem Artikel Erlernte aufbauen!
Wie wäre es mit einem kleinen Messeprototypen oder einem privaten Miniprojekt? Auch das GitHub-Projekt einfach mal auszuchecken und ein wenig am Quellcode [2] herumschrauben kann durchaus interessant sein. Nur so kann man sich ein wirklich eigenes Bild von NativeScript machen.

Wenn es noch Fragen konkret zum Artikel oder zu NativeScript oder mobiler Entwicklung allgemein gibt, stehen wir natürlich gerne zur Verfügung.

Referenzen




Zu den Themen React, NativeScript, Vue und Angular bieten wir sowohl Beratung, Entwicklungsunterstützung als auch passende Schulungen an:

Auch für Ihren individuellen Bedarf können wir Workshops und Schulungen anbieten. Sprechen Sie uns gerne an.

Über Steffen Jacobs

Steffen Jacobs (M.Sc) hat Wirtschaftsinformatik an der Universität Mannheim studiert und ist als Consultant und Software-Entwickler tätig. In seiner Freizeit engagiert er sich im OpenSource Umfeld.

Feedback oder Fragen zu einem Artikel - per Twitter @triondevelop oder E-Mail freuen wir uns auf eine Kontaktaufnahme!

Los geht's!

Bitte teilen Sie uns mit, wie wir Sie am besten erreichen können.