MOBILE WEB APP MIT CORDOVA – PART 2

Wir haben jetzt ja schon mit Part 1 eine kleine schöne Cordova Web App erstellt. Nun ist auffallend, das mit start der App der obere Teil, also die Status Bar von Android immer wieder ausgeblendet wird. Kann man mit leben, muss man aber auch nicht. Problem hier scheint ein nicht ganz fertig geschriebenes Handling zum Fullscreen zu sein.

Android Status Bar wieder sichtbar schalten

Das Problem scheint hier das Cordova Plugin InAppBrowser zu sein. Hier wird das Flag Fullscreen gesetzt sobald der InAppBrowser gestartet wird. Mit einem kleinen hässlichen Corehack, lässt sich das Problem aber wunderbar umgehen. Ich habe in folgenden zwei Dateien jeweils eine Zeile auskommentiert:

node_modules/cordova-plugin-inappbrowser/src/android/InAppBrowser.java
//dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

platforms/android/app/src/main/java/org/apache/cordova/inappbrowser/InAppBrowser.java
//dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

Notifications des Browsergames

Die Notifications haben mich einige Nächte in Schach gehalten und halber zum Wahnsinn getrieben. Dies lag aber eher nicht an fehlenden Cordova Plugins, als eher am Konstrukt meines Browsergames. Hier hatte ich in Laravel native Webpush Notifications eingebaut, welche in den gängigen Browsern auch elegant funktionierten, aber eben nicht in der App. Interessanterweise funktionierten sie aber im Android Chrome Browser. Beim logging der App kam dann aber immer die Fehler:

Notification ist undefined

Navigator.PushManager is null

Chrome Browser != Webview

Dadurch, daß ich das Plugin inAppBrowser benutze verwende ich aber zwangsläufig die Android WebView. Selbst ohne Cordova inAppBrowser wird diese verwendet. Ich hatte keine Möglichkeit gefunden beispielsweise den Chrome ohne die typischen Leisten wie Adresse etc. Anzuzeigen.

Cordova Phonegap-push-plugin … Nerv

Zu Beginn war ich so naiv und dachte ich könnte das Phonegap-push-plugin für einen eigenen Service, bzw. Pusher verwenden. Im Plugin gibt es ja als Platform neben Android, Windows, iOS auch die Plattform Browser. Dabei habe ich lange nicht beachtet, dass Browser auch das gleiche Problem mit dem WebPush. Logisch, bedient sich ja wenn ich es forciere auch der Android WebView. Die Plattform Android interessiert sich hier nicht für alternative Push Services. Wer möchte kann einfach andere hinzufügen wie die Amazon Schnittstelle und viele andere. Aber eine einfache für einen eigenen Push Service habe ich innerhalb von mehreren Nächten nicht finden können. Nach nunmehr ca. sieben Nächten, wenig Schlaf und dem Wahnsinn nahe, bin ich nun dann halt doch über meinen Schatten gesprungen und habe in das Browsergame Firebase eingebaut. Ist relativ einfach. Ein Beitrag dazu wird noch kommen und den alten Beitrag Notifications aus dem Browsergame in das System behandeln.

Cordova Phonegap-push-plugin einrichten.

Mit Firebase war es dann doch in recht kurzer Zeit abzufrühstücken. Warum wollte ich Firebase nicht? Achja Google. Alles sollte bei mir liegen. Ach Scheiß drauf. Solange die Menschen bei FB und co. sich selbst in der Öffentlichkeit für alle sichtbar diffamieren soll mir diese Kleinigkeit, die über Tokens läuft und Anonym ist, nichts ausmachen.

Tatsächlich musste ich nach dem installieren des Plugins mittels:

cordova plugin add phonegap-plugin-push

nur aus Firebase die google-services.json herunterladen und initial im Root ablegen. Beim Start der App bekam ich dann aber oft den Fehler das die google-services.json nicht gefunden werden könne. Toll! Nun als Hinweis, speichert diese Datei ebenfalls in den folgenden Ordnern:

platforms/android/app/google-services.json

Nun lässt sich die App starten. Natürlich noch ohne die gewünschte Push Notification Funktionalität. Diese kommt, bassierend auf dem ersten Teil dieser Reihe: MOBILE WEB APP MIT CORDOVA – PART 1 nun hinzu.

Speichern der RegistrationID

Da ich hier eine Website, mein Spiel,mit der App wrappe, brauche ich die Registrierungstokens auch in der DB meiner Website. Nur so kann ich Ereignissen entsprechend, wie die Fertigstellung eines Gebäudes, dem Nutzer, Spieler eine Notification zukommen lassen. Dafür habe ich eine Funktion wie folgt angelegt:

function saveRegistrationId(app_url, data, token) {
const url = app_url + "/save-device-token";

fsm.app.executeScript({ code:
'(function () {\n' +
' return userId;\n' +
'})();'
}, function(userId) {
if (userId) {
var httpRequest = new XMLHttpRequest();
httpRequest.open('POST', url);

httpRequest.onreadystatechange = function() {
if (httpRequest.readyState>3 && httpRequest.status==200) { console.log(httpRequest.responseText); }
};
httpRequest.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
httpRequest.setRequestHeader('Content-Type', 'application/json');
httpRequest.setRequestHeader('X-CSRF-Token', token[0]);
httpRequest.send(JSON.stringify({ "user_id": userId, "fcm_token": data.registrationId }));
}
});
}

Mit Aufruf dieser Funktion wird die übergebene RegistrationID in der DB gespeichert. Aber eines nach dem anderen. Im Parameter token, wird die von Phonegap-plugin-push zurückgegebene RegistrationId und der RegistrytionType gespeichert und an diese Funktion übermittelt. Token ist eine Variable für ein von Laravel generierten Auth Token. Diesen benötige ich um einen Ajax Call in meine Website zu starten, der auch pushen darf.

Was macht fsm.app.executeScript?

Unser InAppBrowser ist quasi wie eine Sandbox. Vorteil: macht nicht so schnell was kaputt ;). Nachteil wir kommen über die index.js nicht an die Werte, Ids, Objekte etc., aus der Website ran. Jein! mit fsm.app.executeScript, wobei hier fsm mit dem erstellten Objekt zu sehen ist und nicht als Basis Objekt, können wir über unsere index.js JavaScript Code im InAppBrowser ausführen, und das wichtigste, auch einen return zurückerhalten. Vielleicht nicht ganz Elegant gelöst das ganze, aber für ein Nebenher Schmankerl doch nicht sooo doof.

Wo rufe ich die Funktion saveRegistrationId nun aber auf?

Phonegap-plugin-push hat einige Funktionen auf die wir zugreifen können und müssen. Eine davon ist die Registration. Hier wird dann auch die Funktion saveRegistrationId aufgerufen.

push.on('registration', data => {
    saveRegistrationId('https://MEINE_SEITE.de', data, token);
});

Verwirrt? Woher kommt das jetzt? Ich will es erklären. Auch für das ganze Push Handling des Phonegap-plugin-push habe ich eine eigene Funktion erstellt die gecallt werden kann. Diese sieht aktuell wie folgt aus

function CordovaInitSW(token) {

    push = PushNotification.init({
        android: {
            icon: 'file://img/notification-icon.png',
            smallIcon: 'res://notification_icon.png',
        },
        browser: {
        },
    });

    push.on('registration', data => {
        saveRegistrationId('https://MEINE_SEITE.de', data, token);
    });

    push.on('notification', (data) => {
        ...
    });

   push.on('error', (e) => {
       alert(e.message);
   });
   push.on('success', (e) => {
    console.log(e);
   });
}

Im letzten Beitrag hatte ich hier ja https://github.com/q-m/cordova-web-wrap angegeben um ein einigermaßen sinnvolles Handling für den Online Status des Gerätes zu haben. Und genau hier muss ich jetzt dann doch ein wenig darauf eingehen um zu Zeigen wie und wo ich CordovaInitSW aufgerufen habe.

Wir haben in unserer index.js den folgenden Abschnitt, auszugsweise:

openBrowser: function(url) {
var _url = url || this.appLastUrl || LANDING_URL;
this.app = cordova.InAppBrowser.open(_url, "_blank", "location=no,zoom=no,shouldPauseOnSuspend=yes,toolbar=yes,hidden=yes,beforeload=yes");
// Connect state-machine to inAppBrowser events.
this.app.addEventListener("loadstart", wrapEventListener(this.handle.bind(this, "app.loadstart")), false);
this.app.addEventListener("loadstop", wrapEventListener(this.handle.bind(this, "app.loadstop")), false);

Für uns relevant ist das Event loadstop, denn wir können unser Register mit speicherung der RegistrierungsId erst vollführen, wenn wir ein Token zum speichern in Laravel haben. Also warten wir bis das Laden vorbei, fertig ist -> loadstop. Man könnte sich nun durchhangeln um am Schluss zur Funktion onLoaded zu kommen und dort den Aufruf zu erledigen, oder aber man ist so frech und fügt unschöner weise einfach einen weiteren addEventListener hinzu.

this.app.addEventListener("loadstop",  myloadstopListener, false);

Warum ich das gemacht habe? In meinem Spiel gibt es einen Login, und hier in der Funktion myloadstopListener prüfe ich über fsm.app.executeScript ob ich mich auf der Login Seite befinde oder nicht. Denn auf der Login Seite habe ich ja noch keine UserId, SpielerId die ich zu zwischenspeichern verwenden könnte, bzw. es gibt bei mir einfach keinen Sinn jemandem eine Push Message senden zu wollen, der gar nicht mitspielt. Sehen viele anders, weiß ich, ist mir aber egal!

Ich empfange nun Push Messages von Firebase mit phonegap-plugin-push, aber wie zeige ich die nun schön an?

Hierfür gibt es ein simples Plugin, welches easy installiert ist, und ebenfalls einfach zu verwenden ist. Es heißt, trommelwirbel, wer hätte es gedacht: cordova-plugin-local-notifications. Einfach zu installieren mit:

cordova plugin add cordova-plugin-local-notification

Nun können wir einfach in unserer index.js nach der Stelle push.on Notification suchen und dort die Pushbenachrichtigung einbauen:

push.on('notification', (data) => {
    cordova.plugins.notification.local.schedule({
        title: data.title,
        text: data.message,
        attachments: ['file://img/'+data.image],
        icon: 'file://img/logo_circle.png',
        smallIcon: 'res://notification_icon.png',
    });
});

Ausprobieren.´Funktioniert. Bilder anpassen und Spaß haben!

Im nächsten Beitrag möchte ich darauf eingehen wie man für das Login die Daten in der App speichert um dem Besucher das stetige eingeben seiner Daten zu ersparen. Weiterhin wie ein Seiten reload bei Resume, also wieder zurückkommen auf die App zu erledigen ist und was im Laufe der Entwicklung noch so alles passiert ist.