Hybride Apps mit Backend as a Service

Ionic und Firebase: Bau deine eigene Foto-Cloud
Keine Kommentare

Das Cross-Plattform-Framework Ionic erfreut sich sowohl unter Web- als auch unter Mobile-Entwicklern immer größerer Beliebtheit. Zusätzlich ist das Backend as a Service Firebase von Google im Zusammenspiel mit Ionic die perfekte Lösung für Entwickler, die komplexe Anwendungen mit Echtzeitdaten entwickeln möchten.

Ionic ist die Kombination aus dem längst in der Enterprise-Welt angekommenen Webframework Angular und Cordova, einem Framework, das Webseiten in einen Container legt und als mobile Anwendung baut. Hinzu kommen diverse eigene Elemente von Ionic, die es dem Entwickler erlauben, mehr als eine Webanwendung in einer App zu bauen und ein natives Erlebnis für den Endnutzer ermöglichen.

Darüber hinaus wird sich der Artikel ebenfalls mit Firebase beschäftigen, einem Realtime-Backend in der Cloud, das es auch Entwicklern ohne fundierte Serverkenntnisse erlaubt, innerhalb kürzester Zeit eine Backend-Schnittstelle für ihre App bereitzustellen. Innerhalb dieses Artikels wird eine Ionic-Anwendung entwickelt, die Firebase integriert. Es werden sowohl die Authentifizierung über Firebase als auch die Cloud-Datenbank und Storage-Funktionen von Firebase benutzt, um eine simple Foto-Cloud-App zu erstellen.

Start unserer Ionic-App

Nachdem Ionic auf unserer Maschine installiert wurde, ermöglicht es die Command Line Integration (CLI), neue Projekte anzulegen, in denen bereits die nötigsten Dateien angelegt und Abhängigkeiten installiert werden. Zusätzlich können über die CLI mittels des generator-Kommandos (kurz g) neue Dateien an der korrekten Stelle innerhalb des Projekts angelegt werden. In unserem Beispiel benötigen wir drei weitere pages, die eine Seite innerhalb der App darstellen. Zusätzlich erzeugen wir einen provider, der die Schnittstelle zu Firebase sein wird und den Austausch von Daten für uns übernimmt. Zuletzt installieren wir noch weitere npm-Pakete für unsere Ionic-App, in diesem Fall das Paket AngularFire, das eine einfachere Schnittstelle mit besseren Funktionen bereitstellt, sowie das offizielle FirebasePaket.

Da wir innerhalb unserer Anwendung auf die Kamera zugreifen wollen, benötigen wir ebenfalls ein CordovaPlug-in sowie das zugehörige Ionic-Native-Paket. Ionic Native ist hierbei ein Wrapper um den JavaScript-Code des Cordova-Plug-ins, um es innerhalb von Angular besser und natürlicher zu integrieren. Folgen Sie den Kommandos in Listing 1, um jetzt Ihre App anzulegen, die nötigen Dateien zu erstellen und zuletzt die benötigten Pakete zu installieren.

// Erzeuge ein neues Ionic-Projekt
ionic start imageCloud blank
cd imageCloud

// Neue Pages und Provider mit dem Generator erstellen
ionic g page login
ionic g page register
ionic g page home
ionic g provider firebase

// npm-Pakete für Firebase und AngularFire installieren
npm install angularfire2@4.0.0-rc0 firebase —save

// Cordova-Plug-in und Ionic-Native-Paket für Kamera installieren
ionic cordova plugin add cordova-plugin-camera
npm install --save @ionic-native/camera

Nachdem unsere App erzeugt wurde und wir alles angelegt haben, was im weiteren Verlauf benötigt wird, müssen wir das Modul unserer App anpassen, um die verschiedenen Pakete zu laden. Die Datei app.module.ts definiert alle Pakete und Abhängigkeiten unseres Moduls, das unserer App entspricht. Hier müssen nun die entsprechenden npm- und Ionic-Native-Pakete sowie unser selbst erstellter Provider eingetragen werden. Zusätzlich wird für die Verbindung zu Firebase eine Konfiguration angelegt, die dem AngularFire-Modul bei der Initialisierung mitgegeben wird. Bisher haben wir diese Werte noch nicht, im nächsten Schritt werden wir sie allerdings direkt von Firebase ohne Probleme importieren können. Fügen Sie die neuen Elemente in die src/app/app.module.ts wie in Listing 2 gezeigt ein, um alle nötigen Abhängigkeiten zu importieren.

// app.module.ts
// Alle bisherigen Imports bleiben erhalten
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFireDatabaseModule } from 'angularfire2/database';
import { AngularFireModule } from 'angularfire2';
import { FirebaseProvider } from '../providers/firebase/firebase';
import { Camera } from '@ionic-native/camera';

const firebaseConfig = {
  apiKey: "PLACEHOLDER",
  authDomain: "PLACEHOLDER",
  databaseURL: "PLACEHOLDER",
  projectId: "PLACEHOLDER",
  storageBucket: "PLACEHOLDER",
  messagingSenderId: "PLACEHOLDER"
};

@NgModule({
  declarations: [
    MyApp
  ],
   imports: [
    BrowserModule,
    HttpModule,
    AngularFireDatabaseModule,
     AngularFireAuthModule,
    AngularFireModule.initializeApp(firebaseConfig),
    IonicModule.forRoot(MyApp)
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp
  ],
  providers: [
    StatusBar,
    SplashScreen,
    {provide: ErrorHandler, useClass: IonicErrorHandler},
    FirebaseProvider,
    Camera
  ]
})
export class AppModule {}

Eine neue Firebase-App anlegen

Sollten Sie noch keinen Account bei Firebase haben, können Sie diesen kostenlos ohne Probleme anlegen. Firebase wird erst kostenpflichtig, sobald Sie ein höheres Volumen an Daten benötigen. Wenn Sie in Firebase angemeldet sind, befinden Sie sich in der Projektkonsole, wie in Abbildung 1 gezeigt. Hier können Sie Projekte verwalten und anlegen, was wir im nächsten Schritt tun. Klicken Sie dafür auf PROJEKT HINZUFÜGEN und wählen Sie einen Namen sowie den Standort des Servers. Hier ist es sinnvoll, eine Region zu wählen, in der die potenziellen Nutzer Ihrer Anwendung leben, da so die Latenz verringert wird.

Abb. 1: Firebase-Projektkonsole

Abb. 1: Firebase-Projektkonsole

Nachdem das Projekt angelegt wurde, befinden Sie sich in der Übersicht Ihres neuen Projekts. Derzeit benötigen wir die Konfiguration für unser Modul, die wir direkt angezeigt bekommen, wenn wir FIREBASE ZU MEINER WEB-APP HINZUFÜGEN anklicken. Firebase liefert Ihnen hier einen ganzen Block, den Sie in eine Webanwendung integrieren könnten, allerdings benötigen wir für die Konfiguration unserer App nur den config-Block, der bei genauerer Betrachtung bereits exakt aussieht wie der Dummy-Block, der sich derzeit in unserer app.module.ts befindet. Kopieren Sie diese Informationen aus Firebase, ersetzen Sie den Platzhalter innerhalb der Moduldatei, und schon kann Ihre Anwendung auf das Firebase Backend zugreifen.

Zusätzlich muss im Bereich Authentication bei Firebase noch das Anmeldeverfahren Email/Passwort aktiviert werden, danach können wir direkt über unsere App einen Benutzer registrieren und ohne weiteren Code anmelden.

Die Schnittstelle zu Firebase

Innerhalb von Angular gibt es verschiedene Komponenten, die es ermöglichen, die Logik unserer Anwendung besser aufzuteilen. Unser Provider wird allen Code enthalten, um mit Firebase zu interagieren. Auch wenn wir bereits ein weiteres Paket – AngularFire – installiert haben, ist es sinnvoll, diese Interaktionen gebündelt innerhalb eines Providers zu haben, der dann in allen Pages der App verfügbar ist. Innerhalb des Providers implementieren wir alle benötigten Funktionen für den Registrierungs- und Anmeldeprozess und um unsere Bilder hochzuladen. Bereits im Konstruktor des Providers können wir uns auf den authState von Firebase subscriben, da dieser ein Observable ist und wir so jede Änderung am Status direkt erkennen. Sollte ein Benutzer sich also an- oder abmelden, werden wir jeweils den aktuellen Nutzer neu setzen. Außerdem subscriben im weiteren Verlauf der Anwendung auch andere Pages zu diesem Status, um ebenfalls alle Änderungen sofort zu erhalten.

International PHP Conference 2018

Getting Started with PHPUnit

by Sebastian Bergmann (thePHP.cc)

Squash bugs with static analysis

by Dave Liddament (Lamp Bristol)

API Summit 2018

From Bad to Good – OpenID Connect/OAuth

mit Daniel Wagner (VERBUND) und Anton Kalcik (business.software.engineering)

Das eigentliche Registrieren eines neuen Nutzers erfolgt innerhalb einer Zeile, da wir nur die entsprechende Funktion von AngularFire benutzen müssen, um einen Benutzer mit E-Mail und Passwort zu registrieren. Zusätzlich führen wir hier allerdings eine erste Aktion auf die Datenbank bei Firebase aus, bei der wir eine Liste abrufen, die unter dem Knoten /userProfile liegt. Wenn wir bei Firebase einen Benutzer anlegen, ist dieser zwar im System registriert, allerdings haben wir kein wirkliches Profil vom ihm. Daher erzeugen wir direkt nach der Registrierung einen neuen Knoten in der Datenbank. Dessen Key ist die einzigartige uid, die wir nach erfolgreicher Registrierung erhalten.

An dieser Stelle könnten beliebige Informationen gespeichert werden, die das Profil eines Benutzers darstellen. Wir sehen nun das erste Mal, wie der Austausch von Daten mit Firebase abläuft: Alle Daten bei Firebase sind JSON-Objekte, die wir entweder als ein Objekt oder mit der .list()-Funktion abrufen können, um alle Kinder eines Knotens zu erhalten. Firebase wird hier auch den Knoten anlegen, sollte er bisher noch nicht existieren. Mithilfe der update()-Funktion aktualisieren (oder in diesem Fall: setzen) wir die Daten eines Knotens in der Liste, da wir ja bereits einen eigenen Key für das Objekt haben, nämlich die uid. Um einen Benutzer anzumelden, benötigen wir wie bei der Registrierung nur eine Funktion von AngularFire. Sie liefert uns ein Promise mit der Information, ob die Anmeldung erfolgreich war. Das Gleiche gilt auch für das Abmelden innerhalb der App. Zusätzlich wird durch diese Funktionen bei Erfolg der authStatus geändert; zu diesen sind wir ja bereits im Konstruktiv des Providers subscribed.

Am spannendsten ist in diesem Provider die Funktion uploadImage(), bei der wir unsere aufgenommenen Bilder zu Firebase hochladen wollen. An dieser Stelle benötigen wir Zugriff auf den Storage von Firebase, der neben der eigentlichen Datenbank eine einfache Ablage für Dokumente darstellt – perfekt also für unsere Bilder. Auch hier benutzen wir wieder die uid des aktuellen Nutzers, um auf einen Ordner innerhalb unseres Speichers mit der .ref()-Funktion zuzugreifen. Um unser Bild zu speichern, erzeugen wir mit .child() ein neues Objekt im Speicher, in das wir dann per .putString() unsere Bilddaten abspeichern. Diese sind in diesem Fall ein Base64-String, dem wir zusätzlich als contentType weitere Metainformationen mit anheften. Allerdings ist das Hochladen des Bilds nur der erste Schritt. Wir müssen zusätzlich in der Datenbank einen Eintrag anlegen, damit der Benutzer eine Liste seiner Bilder abrufen kann, da es derzeit noch keine Funktion gibt, um über einen Ordner innerhalb des Firebase Storage zu iterieren.

Nach dem Upload erhalten wir von Firebase die Information, unter welchem URL die Datei zu finden ist. Außerdem fügen wir zu unserem Objekt die aktuelle Zeit hinzu, damit wir anzeigen können, wann dieses Bild hochgeladen wurde. An dieser Stelle benutzen wir die .push()-Funktion auf eine Firebase-Liste, die dafür sorgt, dass ein einzigartiger Key für das neue Objekt erstellt wird und unsere Daten ihm zugeordnet werden. Später sind wir durch unsere Datenbankeinträge in der Lage, auf einfachste Weise alle hochgeladenen Objekte des Users anzuzeigen, was durch die letzte Funktion des Providers ermöglicht wird.

Um eine einfache Liste von Daten eines Knotens der Datenbank abzurufen, können wir mit der .list()– Funktion alle Daten zurückgeben. Außerdem deckt diese Funktion ein weiteres Feature von Firebase ab: die Echtzeitdatenbank. Diese Funktion wird uns nicht nur eine Liste mit Knoten liefern, sondern automatisch alle Änderungen mitteilen. Alle bis hierhin beschriebenen Funktionen befinden sich im Projekt in der app/providers/firebase/firebase.ts und sind in Listing 3 abgebildet.

// firebase.ts
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFireDatabase } from 'angularfire2/database';
import * as firebase from 'firebase';

@Injectable()
export class FirebaseProvider {
  user: firebase.User;
  authState: Observable<firebase.User>;

  constructor(private afAuth: AngularFireAuth, public afd: AngularFireDatabase)       {
    this.authState = afAuth.authState;

    this.authState.subscribe(user => {
      this.user = user;
    });
  }

  signUp(email, password, name) {
    return this.afAuth.auth.createUserWithEmailAndPassword(email, password)
      .then(newUser => {
        this.afd.list('/userProfile').update(newUser.uid, { email: email, name: name });
      });
  }

  loginUser(email, password) {
    return this.afAuth.auth.signInWithEmailAndPassword(email, password);
  }

  logoutUser() {
    return this.afAuth.auth.signOut();
  }

uploadImage(imageData) {
    let imgName = this.user.uid + '_' + new Date().getTime();

    return firebase.storage().ref(this.user.uid).child(imgName + '.png').putString(imageData, 'base64', { contentType: 'image/png' })
      .then(result => {
        let imgInfo = { imageURL: result.downloadURL, created: new Date().getTime() }
        return this.afd.list('/userProfile/' + this.user.uid + '/images').push(imgInfo);
      }, err => {
        // Handle Error
      })
  }

  getImages() {
    return this.afd.list('/userProfile/' + this.user.uid + '/images');
  }
}

Benutzerregistrierung und Anmeldung

Die Log-in-Seite stellt den Einstieg in unsere App dar. Zur Anmeldung benötigen wir die Felder E-MAIL und PASSWORT sowie einen Button, um zur Seite der Registrierung zu gelangen. Entscheidend ist das Log-in-Formular, das ein Reactive-Formular ist. Dieser Formulartyp ist seit Angular 2 verfügbar und erlaubt es, ein Formular sowie die Validierung innerhalb der Komponente in JavaScript zu definieren, während normalerweise die komplette Logik eines Formulars im HTML-Code für die View zu finden ist. Unser Formular wird vom form-Element, das die formGroup definiert, umschlossen. Wir legen sie innerhalb des Controllers ab. Außerdem nutzen wir die submit-Aktion, also die Funktion, die aufgerufen wird, wenn das Formular abgeschickt werden soll.

Die eigentlichen Felder innerhalb des Formulars werden über den formControlName angesteuert, der sich ebenfalls auf die Elemente innerhalb des Formulars bezieht, das aus dem JavaScript kommt. Zusätzlich können wir auf den Status der Validierung jedes Elements zugreifen, wodurch wir gezielt Fehlermeldung für die Felder anzeigen können, sollte der aktuelle Wert nicht dem entsprechen, was wir als valide definiert haben. Zusätzlich befindet sich in der Validierung das Feld dirty, das uns zeigt, dass der User dieses Feld bereits benutzt hat. Sollte dieser Wert nicht überprüft werden, wären bereits zu Beginn alle Felder deaktiviert, was aus UI-Sicht natürlich nicht sonderlich ansprechend wirkt. Ebenso kann der Button zum Abschicken des Formulars deaktiviert werden, bis das gesamte loginFor-Objekt einen validen Status erreicht hat. Unterhalb des Formulars positionieren wir einen weiteren Button, über den wir die Registrierungsseite anzeigen wollen. Der komplette Code für die app/pages/home/home.html ist in Listing 4 zu finden.

<!-- login.html -->
<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Foto Cloud
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>

  <div class="credentials-form">
    <form [formGroup]="loginForm" (submit)="loginUser()" novalidate>

      <ion-item class="form-input">
        <ion-label stacked>Email</ion-label>
        <ion-input formControlName="email" type="email" placeholder="john@doe.com" [class.invalid]="!loginForm.controls.email.valid && loginForm.controls.email.dirty"></ion-input>
      </ion-item>

      <ion-item class="form-input">
        <ion-label stacked>Passwort</ion-label>
        <ion-input formControlName="password" type="password" placeholder="123456" [class.invalid]="!loginForm.controls.password.valid && loginForm.controls.password.dirty"></ion-input>
      </ion-item>

      <br><br>
      <button ion-button block type="submit" color="primary" [disabled]="!loginForm.valid" round>
      Anmelden
    </button>

    </form>
    <button ion-button block clear color="dark" (click)="goToSignup()">
    Neuen Account erstellen
  </button>
  </div>
  
</ion-content>

Als wir zu Beginn die Pages erzeugt haben, wurden bereits die entsprechenden Controller und Styling-Dateien angelegt, daher können wir direkt innerhalb der zugehörigen home.scss ein erweitertes Styling für den Log-in definieren. Durch kleinere Änderungen am Style ist es so möglich, den Views ein eigenes Styling zu verpassen. Hier sind der Kreativität natürlich keine Grenzen gesetzt. Für unser Beispiel reicht allerdings das in Listing 5 beschriebene Styling der Komponenten, um das Formular mittig innerhalb eines Containers anzuzeigen und invaliden Eingabefeldern einen roten Rahmen unten zu verleihen.

// login.scss
page-login {
  .credentials-form {
    margin-top: 20%;
    background: rgba(236, 236, 236, 0.9);
    padding: 20px;
    border-radius: 20px;
  }
  .form-input {
    background: transparent;
  }
  .invalid { 
    border-bottom: 1px solid #ff0000; 
  }
}

Nachdem wir innerhalb der HTML-Datei bereits das Formular benutzt haben, legen wir es jetzt tatsächlich im zugehörigen Controller an. Unser loginForm ist eine FormGroup, das heißt, es beschreibt eine Gruppe von Eingabefeldern und deren Eigenschaften. Über den FormBuilder von Angular können wir im Konstruktor der Klasse die beiden Felder email und password der Gruppe definieren. Beide Felder haben zu Beginn den Wert eines leeren Strings, eine vorgegebene minimale Länge von sechs Zeichen und sind zudem beide verpflichtend auszufüllen, damit das Formular als valide gekennzeichnet wird. Die restliche Validierungslogik übernimmt Angular intern. Wir haben bereits die Zustände in der View benutzt, um nicht valide Zustände zu markieren oder einen Button auszuschalten, solange das gesamte Formular noch nicht valide ist.

An dieser Stelle könnten ebenfalls verschiedene andere Validierungen stattfinden; es könnten etwa ganze Pattern eines regulären Ausdrucks benutzt werden, um einzelne Eingabefelder zu validieren. Zusätzlich befindet sich in der Klasse der Code, um zu unserer Registrierungsseite zu gelangen. Die Navigation innerhalb einer Ionic-Anwendung erfolgt hier sehr simpel über den NavController, bei dem wir einfach eine weitere Page zum View-Stack hinzufügen können. Dadurch wird die neue Seite angezeigt, und Ionic erzeugt automatisch einen ZURÜCK-Pfeil in der Bar oben. Ionic benutzt hier im Vergleich zu Angular-Webanwendungen ein anderes Schema, da eine Navigation über URLs innerhalb einer App weniger sinnvoll ist als im Web. Diese vereinfachte Navigation erleichtert zwar die Entwicklung einer Ionic-App, erschwert allerdings die Umwandlung in ein generelles Angular-Webprojekt, da hier ein anderes Routing-Schema benutzt wird.

Die zentrale Funktion unserer Klasse ist natürlich der eigentliche Log-in, bei dem wir auf unseren zuvor erstellten Firebase-Provider zurückgreifen können und einfach die entsprechende Funktion mit den Werten unseres Formulars aufrufen. Nach einem erfolgreichen Log-in könnten wir hier zur nächsten Seite für eingeloggte Benutzer navigieren. Allerdings werden wir dieses Verhalten an einer höheren Stelle gesammelt behandeln, wodurch wir nach dem Log-in hier keine weitere Funktion aufrufen müssen und automatisch zur nächsten Seite gelangen, da sich der Status des Benutzers innerhalb der App geändert hat. Einzig der Fehlerfall muss hier abgedeckt werden, was wir durch einen einfachen Alert von Ionic machen. Der komplette Code für app/pages/home/home.ts befindet sich in Listing 6.

// login.ts
import { FirebaseProvider } from './../../providers/firebase/firebase';
import { Component } from '@angular/core';
import { IonicPage, NavController, AlertController } from 'ionic-angular';
import { FormBuilder, Validators, FormGroup } from '@angular/forms';

@IonicPage()
@Component({
  selector: 'page-login',
  templateUrl: 'login.html',
})
export class LoginPage {
  public loginForm: FormGroup;

  constructor(public navCtrl: NavController, private formBuilder: FormBuilder, private alertCtrl: AlertController, private firebaseProvider: FirebaseProvider) {
    this.loginForm = formBuilder.group({
      email: ['', Validators.compose([Validators.minLength(6), Validators.required])],
      password: ['', Validators.compose([Validators.minLength(6), Validators.required])]
    });
  }

  loginUser() {
    this.firebaseProvider.loginUser(this.loginForm.value.email, this.loginForm.value.password).then(data => {
      // Wir sind erfolgreich eingeloggt!
    }).catch(error => {
      let alert = this.alertCtrl.create({
        title: 'Error',
        message: error.message,
        buttons: [
          {
            text: "Ok",
            role: 'cancel'
          }
        ]
      });
      alert.present();
    });
  }

  goToSignup() {
    this.navCtrl.push('RegisterPage');
  }
}

Die Registrierung folgt dem gleichen Schema wie der Log-in, hier kommt einzig noch ein weiteres Feld für den Namen des Benutzers dazu, das wir dann in seinem Knoten in der Datenbank mit abspeichern können. Außerdem fügen wir auch hier ein erweitertes Styling hinzu, der Code für die app/pages/register/register.html und register.scss findet sich in Listing 7.

<!-- register.html -->
<ion-header>
  <ion-navbar color="primary">
    <ion-title>
      Neuer Account
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>

  <div class="credentials-form">
    <form [formGroup]="signupForm" (submit)="signupUser()" novalidate>

      <ion-item class="form-input">
        <ion-label stacked>Name</ion-label>
        <ion-input formControlName="name" type="text" placeholder="John Doe" [class.invalid]="!signupForm.controls.name.valid && signupForm.controls.name.dirty">
        </ion-input>
      </ion-item>

      <ion-item class="form-input">
        <ion-label stacked>Email</ion-label>
        <ion-input formControlName="email" type="email" placeholder="john@doe.com" [class.invalid]="!signupForm.controls.email.valid && signupForm.controls.email.dirty"></ion-input>
      </ion-item>

      <ion-item class="form-input">
        <ion-label stacked>Passwort</ion-label>
        <ion-input formControlName="password" type="password" placeholder="123456" [class.invalid]="!signupForm.controls.password.valid && signupForm.controls.password.dirty"></ion-input>
      </ion-item>

      <br><br>
      <button ion-button block type="submit" [disabled]="!signupForm.valid" round>
      Account erstellen
    </button>

    </form>

  </div>
</ion-content>

<!-- register.scss -->
page-register {
  .credentials-form {
    margin-top: 20%;
    background: rgba(236, 236, 236, 0.9);
    padding: 20px;
    border-radius: 20px;
  }
  .form-input {
    background: transparent;
  }
  .invalid { 
    border-bottom: 1px solid #ff0000; 
  }
}

Wie beim Log-in benutzen wir auch im Controller für die Registrierung ein Formular, das wir im Konstruktor definieren. Um den Benutzer zu registrieren, wird unsere vorher implementierte Funktion des Providers benutzt. Nach einer erfolgreichen Registrierung werden wir automatisch aufgrund des neuen Status des Benutzers zur nächsten Seite kommen, einzig der Fehlerfall muss hier wieder behandelt werden. Der Code für die app/pages/register/register.ts befindet sich in Listing 8.

import { Component } from '@angular/core';
import { IonicPage, NavController, AlertController } from 'ionic-angular';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { FirebaseProvider } from './../../providers/firebase/firebase';

@IonicPage()
@Component({
  selector: 'page-register',
  templateUrl: 'register.html',
})
export class RegisterPage {
  public signupForm: FormGroup;

  constructor(public navCtrl: NavController, public firebaseProvider: FirebaseProvider,
    public formBuilder: FormBuilder, public alertCtrl: AlertController) {
    this.signupForm = formBuilder.group({
      email: ['', Validators.compose([Validators.minLength(6), Validators.required])],
      password: ['', Validators.compose([Validators.minLength(6), Validators.required])],
      name: ['', Validators.compose([Validators.minLength(2), Validators.required])],
    });
  }

  signupUser() {
    this.firebaseProvider.signUp(this.signupForm.value.email, this.signupForm.value.password, this.signupForm.value.name)
      .then(() => {
        // Benutzer erfolgreich angemeldet
      }, (error) => {
        let alert = this.alertCtrl.create({
          title: 'Error',
          message: error.message,
          buttons: [
            {
              text: "Ok",
              role: 'cancel'
            }
          ]
        });
        alert.present();
      });
  }
}

Bisher haben wir bei der Registrierung und Anmeldung den erfolgreichen Fall des Promise nicht implementiert, da wir die Änderung des Userstatus an anderer Stelle ebenfalls abfangen und somit an höchster Stelle eine neue Page für unsere App setzen können. Dieses Verhalten können wir in der app.component.ts implementieren, indem wir uns auf den authState unseres Providers registrieren. Sollten wir einen Nutzer bekommen, so setzen wir die aktuelle rootPage der App auf die HomePage, sollten wir keinen Nutzer haben, wird der Log-in angezeigt. Somit wird automatisch abhängig vom State die korrekte Seite angezeigt und selbst bei einem Neustart der App ein vorher angemeldeter Benutzer direkt eingeloggt und zur eigentlichen Verwaltungsseite nach dem Log-in geschickt. Den Code für diese Weiterleitung sehen Sie in Listing 9.

// app.component.ts
import { Component } from '@angular/core';
import { Platform } from 'ionic-angular';
import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';
import { FirebaseProvider } from './../providers/firebase/firebase';

@Component({
  templateUrl: 'app.html'
})
export class MyApp {
  rootPage: any;

  constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, firebaseProvider: FirebaseProvider) {
    platform.ready().then(() => {
      statusBar.styleDefault();
      splashScreen.hide();

      firebaseProvider.authState.subscribe(user => {
        if (user) {
          this.rootPage = 'HomePage';
        } else {
          this.rootPage = 'LoginPage';
        }
      });
    });
  }
}

Nachdem alles angelegt wurde, haben wir jetzt bereits eine Benutzerverwaltung und einen Log-in-Bereich wie in Abbildung 2 zu sehen.

Abb. 2: Der Log-in-Bereich

Abb. 2: Der Log-in-Bereich

Fotos aufnehmen, hochladen und anzeigen

Die eigentliche Funktionalität liegt hinter der Anmeldung, denn bisher haben wir nur ein generelles System implementiert. Um unsere Funktionalität darzustellen, müssen wir dem Nutzer einerseits eine Liste seiner aufgenommenen und hochgeladenen Bilder, andererseits einen Button, über den wir neue Bilder hinzufügen können, anzeigen. Innerhalb der app/pages/home/home.html iterieren wir mit einem *ngFor über alle unsere Bilder und fügen die asynPipe hinzu, die dafür sorgt, dass neue Daten automatisch angezeigt werden. Mittels eines ion-fabButtons ermöglichen wir das Auswählen neuer Bilder, zudem fügen wir einen Log-out-Button zur Navigationsleiste hinzu. Der Code für unsere View sowie das zugehörige CSS finden sich in Listing 10.

<!-- home.html -->
<ion-header>
  <ion-navbar color="primary">
    <ion-title>Meine Foto Cloud</ion-title>
    <ion-buttons end>
      <button ion-button icon-only (click)="logout()"><ion-icon name="log-out"></ion-icon></button>
    </ion-buttons>
  </ion-navbar>

</ion-header>

<ion-content padding>

  <ion-card *ngFor="let img of images | async">
    <img [src]="img.imageURL">
  </ion-card>

<ion-fab right bottom>
    <button ion-fab mini color="primary" [disabled]="uploadActive" (click)="captureImage()" class="capture-btn"><ion-icon name="camera"></ion-icon></button>
    <button ion-fab mini color="secondary" [disabled]="uploadActive" (click)="loadGallery()" class="lib-btn"><ion-icon name="image"></ion-icon></button>
  </ion-fab>
</ion-content>

<!-- home.scss -->
page-home {
  .capture-btn {
    box-shadow: 2px 2px 13px -1px rgba(43,24,43,0.7);
  }
  .lib-btn {
    box-shadow: 2px 2px 13px -1px rgba(43,24,43,0.7);
  }
}

Jetzt müssen wir nur noch in der zugehörigen Klasse die Bilder von unserem Provider abrufen. Da wir eine Liste vom Type FirebaseListObservable zurückbekommen, werden neue Bilder automatisch angezeigt. Zusätzlich implementieren wir hier die Funktion, um Bilder wahlweise direkt von der Kamera oder aus unserer Fotobibliothek hochzuladen. Auch hier können wir wieder auf den Provider für das eigentliche Hochladen zurückgreifen, da dort die komplette Logik bereits implementiert wurde. Der Code für unsere app/src/pages/home/home.ts findet sich in Listing 11.

// home.ts
import { FirebaseProvider } from './../../providers/firebase/firebase';
import { Component } from '@angular/core';
import { IonicPage, NavController, ToastController } from 'ionic-angular';
import { Camera, CameraOptions } from '@ionic-native/camera';
import { FirebaseListObservable } from 'angularfire2/database';

@IonicPage()
@Component({
  selector: 'page-home',
  templateUrl: 'home.html',
})
export class HomePage {
  images: FirebaseListObservable;

  constructor(public navCtrl: NavController, private camera: Camera, private toastCtrl: ToastController, private firebaseProvider: FirebaseProvider) { }

  ionViewDidLoad() {
    this.firebaseProvider.authState.subscribe(user => {
      if (user) {
        this.images = this.firebaseProvider.getImages();
      }
    });
  }

  captureImage() {
    this.loadPicture(this.camera.PictureSourceType.CAMERA);
  }

  loadGallery() {
    this.loadPicture(this.camera.PictureSourceType.PHOTOLIBRARY);
  }

  loadPicture(source) {
    const options: CameraOptions = {
      quality: 50,
      destinationType: this.camera.DestinationType.DATA_URL,
      sourceType: source,
      correctOrientation: true,
      allowEdit: false,
      saveToPhotoAlbum: true
    };

    this.camera.getPicture(options).then(imageData => {
      this.firebaseProvider.uploadImage(imageData)
        .then((res) => {
          this.showToast('Bild erfolgreich hochgeladen!');
        }, err => {
          this.showToast('Das hat leider nicht geklappt :(');
        })
    }, err => {
      // Handle Error
    });
  }

  showToast(msg) {
    let toast = this.toastCtrl.create({
      message: msg,
      duration: 2000
    });
    toast.present();
  }

  logout() {
    this.firebaseProvider.logoutUser();
  }
}

Der Code für unsere App ist nun komplett. Innerhalb des eingeloggten Bereichs kann man jetzt Bilder hochladen und innerhalb einer Liste ansehen (Abb. 3).

Abb. 3: Bereich der App nach dem Log-in

Abb. 3: Bereich der App nach dem Log-in

Firebase-Sicherheitseinstellungen

Unsere Daten bei Firebase sind bisher theoretisch für jeden erreichbar. Um das zu ändern, gibt es bei Firebase Security-Rules, die definieren, wie auf welche Daten innerhalb der Datenbank zugegriffen werden darf. Innerhalb des Menüpunkts DATABASE gibt es einen Reiter REGELN, bei dem wir das folgende Codebeispiel hinterlegen können:

{
  "rules": {
    "userProfile": {
      "$uid": {
        ".write": "$uid === auth.uid",
        ".read": "$uid === auth.uid"
      }
    }
  }
}

Dadurch ist es nun nur den Nutzern erlaubt, auf einen bestimmten Knoten zuzugreifen, wenn deren auth.uid der Knoten-ID entspricht. An dieser Stelle gibt es noch diverse weitere Funktionen und Werte von Firebase, die genutzt werden können, um die Datenbank und den Speicher für Daten mit Regeln zu beschützen.

Fazit: Ionic + Firebase = Dreamteam?

Unsere Ionic-App kann jetzt problemlos für iOS und Android gebaut werden, zusätzlich haben wir ein vollfunktionales Backend, für das wir keine weitere Programmiersprache lernen mussten. Insgesamt stellen Ionic und Firebase derzeit eine der beliebtesten Kombinationen dar, wenn es um die Entwicklung von Cross-Plattform-Anwendungen mit einem Echtzeit-Backend geht. Wer also schnelle Ergebnisse sehen will, kann mit diesem Dreamteam selbst komplexe Apps innerhalb kürzester Zeit implementieren.

PHP Magazin

Entwickler MagazinDieser Artikel ist im PHP Magazin erschienen. Das PHP Magazin deckt ein breites Spektrum an Themen ab, die für die erfolgreiche Webentwicklung unerlässlich sind.

Natürlich können Sie das PHP Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu:
X
- Gib Deinen Standort ein -
- or -