Navigator! Render mir eine Navigation bar

Der letzte Post verspricht als nächsten Schritt eine Detail View. Ich möchte dieses Versprechen erfüllen, aber bevor wir etwas User Interaktionsmöglichkeiten in unseren schönen RSS Reader bauen können, müssen wir noch eine kleine strukturelle Änderung an unserer App vornehmen.

Grundsätzlich bietet uns React Native zwei verschiedene Komponenten für die Implementierung einer Navigation Bar. Für alle die sich jetzt wundern wofür wir jetzt eine Navigation Bar brauchen, kann ich diesen Link empfehlen.

Wir haben also zur Auswahl Navigator und NavigatorIOS. Beide bieten uns die Möglichkeit einen Navigationsstapel zu realisieren, jedoch ist Variante 1 quasi Crossplattformfähig. Vielleicht ist jedoch der größte Pluspunkt, neben der Möglichkeit die Navigation Bar nach belieben anpassen zu können - geschuldet durch die Tatsache, dass es sich um eine komplette JavaScript Lösung handelt - diese Komponente vom React Native Team entwickelt und gewartet wird.

Variante 2 (NavigatorIOS) ist quasi nur ein Wrapper auf die native Navigation Bar, funktioniert nur auf iOS und ist eine third party Komponente. Mit dem großen Vorteil jedoch, dass wir uns nicht noch erst eine NavigationBarerstellen müssen. Aus reiner Faulheit nehmen wir jetzt mal Variante 2.

 var ReactListView = React.createClass({
     render: function() {
         return (
             <NavigatorIOS
                initialRoute={{
                    title: 'Image of the day',
                    component: PlanetListView
                }}
                style={styles.navigation}
             />
             );
}
});

Wir müssen eigentlich nicht viel machen - was grundsätzlich nichts schlechtes ist - wenn wir eine entsprechenden Navigator einbinden möchten. Der wichtigste Punkt ist eigentlich die Konfiguration der initial Route bzw. Szene. Damit wir jedoch noch eine schicke Navigation Bar erhalten müssen wir unseren Code noch um folgendes ergänzen

var styles = StyleSheet.create({  
    navigation : {
        flex: 1
    }
})

Et voila. Unser Ergebnis sollte ungefähr so aussehen

Das war doch jetzt kurz und schmerzlos oder? Schmerzen kommen evtl. im nächsten Artikel oder auch nicht. Wer weiss ;)

Natives XML - oder so ähnlich

Nachdem wir beim letzten mal unseren kleinen RSS Reader bereits mit statischen Daten versorgt hatten, würde ich mich heute gerne um die Anbindung des eigentlichen XML Streams kümmern.

JSONxml

Grundsätzlich haben wir ja keine schwere Implementationsrunde vor uns, jedoch stehen wir vor einem kleinen Problem. Der Feed ist in XML.
Logisch - ist ja auch ein RSS Feed. Eleganter wäre es - und vor allem einfacher - wenn wir den Feed als JSON abrufen könnten, also müssen wir das XML parsen und zu JSON wandeln.
Natürlich würde es auch reichen, wenn wir das XML einfach parsen und in irgendein Format bringen könnten, womit wir arbeiten können. Jedoch erwartet unsere App ja bereits die Daten als einfache JavaScript Objekte.

Mögliche Lösungen

  1. Wir schreiben einen eigenen XML Parser in JavaScript.
    • Nein.
  2. Wir nutzen einen zwischen Webservice, der das XML holt und uns in JSON bereitstellt. Könnte man in Node.js sicherlich einfach und schnell bauen.
    • Gute Idee, aber in unserem Fall etwas zuviel des Guten
  3. Oben wurde Node.js erwähnt! Wir nutzen einfach ein entsprechendes Node Modul
    • Noch bessere Idee. Leider läuft React Native nicht in der Node.js Runtime sondern in der JavaScriptCore Runtime, d.h. kein Browser Object Model - keine DOM, kein window, kein document
  4. NativeModules - geiles Buzzword.
    • Yes. So machen wir es.

NativeModules

React Native gibt uns die Möglichkeit nativen Code in unsere Anwendung einzubinden. Und das ist gut! Gerade in unserem Fall! D.h. wir sind in der Lage auf das komplette iOS SDK zuzugreifen und können uns hier dann auch bereits von Haus gelieferten Funktionen/Methoden/Klassen bedienen.

NativeModules ist jetzt nicht nur für unser kleines XML Problem interessant, in der Praxis könnten wir so auch zum Bsp. eine SQLite DB ansteuern, falls uns der LocalStorage nicht ausreichen sollte.

Natürlich muss man jetzt etwas Objective-C beherrschen, ich bemühe mich aber, den eigentlich Code auf das geringste zu minimieren.

OGXmlJsonParser

Ich nutze in unserem Projekt eine kleine Helferklasse - XMLDictionary von Nick Lockwood zum parsen des XML Strings in ein NSDictionary. Grund hierfür: Wir sparen uns jede Menge Boilerplatecode, welche der NSXMLParser mitbringen würde.

Für den eigentlichen Export einer Methode müssen wir folgende Kriterien erfüllen:

  • Unsere Klasse muss das Protokoll RCTBridgeModule implementieren
  • Wir müssen in der Klasse das Makro RCT_EXPORT_MODULE() aufrufen
  • Mittels RCT_EXPORT_METHOD() die entsprechende Methode exportieren

Der Aufruf der exportierten Methode findet dann nach folgendem Schema statt

NativeModules.MeineKlasse.MeineMethode  

Als Beispiel einmal unsere XML zu JSON Parser Klasse

@interface OGXmlJsonParser : NSObject <RCTBridgeModule>
@end

@implementation OGXmlJsonParser

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(transformXml:(NSString *)xmlString callback:(RCTResponseSenderBlock)callback) {  
  // parse XML string to NSDictionary
  NSDictionary *dictionary = [[XMLDictionaryParser sharedInstance] dictionaryWithString:xmlString];

  // convert NSDictionary to NSData
  NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:nil];

  // Return NSData as NSString
  NSString *jsonString =  [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
  callback(@[jsonString]);
}

@end

Wir übergeben der Methode den aus dem Web gefischten XML String. Dieser wird in ein NSDictionary geparsed, zu einem NSData Objekt gewandelt um dann vom NSJSONSerializer zu einem JSON Objekt transformiert zu werden. (Ich überspringe mal den Schritt von NSData zu NSString...)

Eingebunden ;)

Am Ende sieht es wie folgt aus

loadRssFeed: function() {  
        fetch(RSS_URL)
            .then((response) => response.text())
            .then((text) => {
                NativeModules.OGXmlJsonParser.transformXml(text, (jsonString) => {
                    var data = JSON.parse(jsonString);
                    this.updateDataSource(data.channel.item);
                });
            })
            .done();
    }

Wir greifen uns den XML Feed per Fetch API, übergeben an das NativeModule und aktualisieren unsere Datasource.

Eigentlich nicht übel

Grundsätzlich bietet diese Möglichkeit uns viele Freiheiten, zumal wir auch in der Lage sind Custom UIViews einzubinden. Jedoch stellt sich mir eine Frage auf, die dem ganzen einen faden Beigeschmack gibt:

Was machen wir, wenn weitere Plattformen dazu kommen? Wir werden sehen, was man sich bei Facebook dazu überlegt hat.

Beim nächsten mal schauen wir uns mal an, ob wir unserer kleinen App nicht eine Detailview verpassen können. Den fertigen Code findet ihr natürlich wieder auf Github.

Image of the Day - RSS Reader mit React Native

Der erste Artikel zum Thema React Native teaserte es schon an: Wir bauen einen RSS Feed Reader, welcher in einer ListView dargestellt werden soll.

Zum Start implementieren wir eine einfache ListView, welche uns ein paar Beispieldaten vom Nasa Image of the Day RSS Feed anzeigt. (Die haben manchmal echt schöne Bildchen)

Lets build

Wir generieren mit der React Native CLI zuerst ein Projekt - ReactListView (jaja… ich weiss. Kreativ!) - und legen direkt eine zweite JS Datei an: listView.js

Direkt zu Beginn der Implementierung lagern wir Komponenten in eigene Dateien aus. Dies hilft später bei der Organisation und vermeidet von mir heiß geliebte Tausendzeiler.
Ausserdem ist es ganz nachdem React Mantra: Denk in Komponenten.

Eingebunden werden Komponenten aus anderen Dateien mittels require

var PlanetListView = require('./listView.js');

var React = require('react-native');  
var {  
  AppRegistry,
  StyleSheet,
  Text,
  View,
} = React;

Fürs erste ist unsere ListView auch unsere Root View, also kann auch getrost der generierte Code in der index.ios.js ersetzt werden

var ReactListView = React.createClass({  
  render: function() {
    return (
      <PlanetListView />
    );
  }
});

Auch die Styles schmeissen wir raus. Was wir nicht brauchen, kann weg. Räumt euren Code auf. Lasst nichts liegen, es wird am Ende nur ein Berg voller Codeleichen und Schnipsel. Wozu unnötig den Ballast mittragen?

Wie sofort sichtbar ist durch die direkte Trennung unserer ListView Komponente die Codezeilenanzahl in der index.ios.js überschaubar.

Statische Daten - Und warum überhaupt eine ListView?

Für den ersten Teil verwenden wir statische Daten, welche wir am Anfang der Datei listView.js definieren. Wir wollen erst das Grundgerüst fertigstellen und uns erst später mit der Anbindung an den Feed auseinander setzen. Bei diesem Projekt kommt uns hier auch zu gute, dass die ListView sich nicht dafür interessiert, woher die Daten kommen, sofern sie immer das gleiche Format haben.

Warum wir überhaupt eine ListView verwenden und nicht einfach eine endlose Anzahl von einfachen Views mit Child Objekten ist simpel: Performance.

Grundsätzlich sind ListViews - in iOS wäre das die UITableView, in Android ListView - so gestrickt, dass Sie immer nur die Anzahl Zeilen wirklich renderen, die gerade zu sehen sind. D.h. wenn die Liste 1000 Einträge hat, aber sichtbar nur 3 sind, dann werden auch nur diese 3 im Speicher vorgehalten. Das trägt zum allgemeinen Wohlsein aller Beteiligten - User, App und Entwickler- bei. Dies wird mit Techniken wie View Recyling und anderen kleinen Schmankerln erreicht.

Wer mehr Infos braucht, bitte und noch mehr bitte.

Zellen Layout

Wir wollen in jeder Zelle erst das Bild und da drunter den Titel des jeweiligen Artikels/Bildes darstellen. Hierzu erstellen wir uns eine Funktion in unserer ListView Komponente:

renderArticle: function(article) {  
        return (
            <View style={styles.container}>
                <View style={styles.cellContent}>
                    <Image 
                        style={styles.enclosure}
                        source={{uri:article.enclosure}} 
                        resizeMode={Image.resizeMode.cover}
                        />  
                    <Text style={styles.title}>{article.title}</Text>
                </View>
            </View>
        );
    },

Legen wir zuerst unser Augenmerk auf die Image Komponente. Über das Attribut source können wir der Komponente eine URI übergeben, das reicht React in diese Fall, sich das entsprechende Bild zu laden und darzustellen. Wir möchten jedoch auch sicher stellen, dass das Bild korrekt skaliert wird, dies stellen wir sicher über den resizeMode. In diesem Fall passiert jedoch die meiste Action in den Styles:

Zunächst definieren wir einen Hauptcontainer über den Style container welcher unser Elternelement darstellt und so gesehen unsere eigentliche Zelle ist.

    container: {
        flex: 1, // nimm den ganzen Platz ein
        flexDirection: 'row', // rendered weitere Element untereinander
        alignItems: 'center', // zentrier alle Kindelemente
        justifyContent: 'center', // zentriere Content
        padding: 25,
        marginRight: 10,
        marginLeft: 10,
        marginBottom: 15,
        backgroundColor: '#e2e2e2',
    },

Die interessanten Punkte habe ich einmal mit einem Kommentar versehen, wie man hier schön sehen kann, nutzen wir in React Native das Flexbox Layouts. Was im Grunde eine Implementierung der CSS Flexible Layout Spec ist. Im Grunde ist es ein weiterer CSS Layoutmodus, der am Ende dem Entwickler dabei helfen soll, einfacher Layouts für verschiedene Bildschirmgrößen zu erstellen. Empfehlenswerter Link zum Thema
Für uns ist vielleicht wichtig zu erwähnen: CSS Eigenschaften die mit einem '-' getrennt sind, müssen wir in eine CamelCase Variante wandeln.

margin-bottom -> marginBottom  
background-color -> backgroundColor  
font-weight -> fontWeight  

Bei Positionsbedingten Zahlenangaben gehen wir immer von Pixeln aus.

Lifecycle Funktionen

Um unsere ListView mit Daten zu befüllen, brauchen wir eine ListView.DataSource. Diese wird in der Methode getInitialState einmal initialisiert. React Komponenten benutzen den State zum persistieren von Eigenschaften/Daten. Definieren wir also im State eine Eigenschaft, haben wir in während der Laufzeit immer Zugriff darauf.

Achtung: Der State jeder Komponente ist private und mutable.

    getInitialState: function() {
        var dataSource = new ListView.DataSource({
            rowHasChanged: (row1, row2) => row1 !== row2
        });
        return {
            dataSource: dataSource,
            loaded: false
        };
    },

Ausserdem halten wir in einer Variable fest, ob wir Daten bereits geladen haben oder nicht. So sind wir in der Lage einen "Lade Screen" anzuzeigen, damit der User immer ein Feedback zurückbekommt, dass die App auch arbeitet/etwas tut/einfach noch da ist.

Die render Funktion spricht für sich selbst. Sind Daten geladen, rendern wir unsere ListView mit unserer im State definieren DataSource, wenn nicht: Zeig uns unsere ProgressView.

render: function() {  
        if (this.state.loaded) {
            return (
                <ListView
                    dataSource={this.state.dataSource}
                    renderRow={this.renderArticle}
                    style={{marginTop: 25}} 
                />
            );
        } else {
            return this.progressView();
        }
    },

Interessant ist die Funktion componentDidMount diese Funktion wird aufgerufen sobald die Komponente geladen ist und sichergestellt, dass diese auch nur einmal ausgeführt wird. In unserem Fall also perfekt um unseren RSS Feed zu laden und unsere ListView mit Daten zu befüllen:

componentDidMount: function() {  
        this.loadRssFeed();
    },

Im ersten Schritt passiert hier jedoch nicht viel, this.loadRssFeed ruft nur updateDataSource auf, welches unserer DataSource nur unsere Beispieldaten übermittelt. Klingt jetzt total langweilig - ist es aber nicht!

updateDataSource: function(newData) {  
        this.setState({
            dataSource: this.state.dataSource.cloneWithRows(newData),
            loaded: true,
        });
    },

Wir aktualisieren die Daten der DataSource über die Funktion this.setState. Der Grund hierfür ist einfach: Sobald in einer React Komponente der State neu gesetzt/verändert wird, wird diese komplett neugerendert. Da wir unsere Daten im State vorhalten, bleiben diese nach einem neuen rendern uns erhalten und präsentieren uns dann also auch eine Liste mit 2 Bildern.

Wrap up

Damit wären wir für unseren ersten Teil auch am Ende angekommen. Natürlich braucht Ihr nicht alles abtippen, dass Projekt findet Ihr auf Github. So sollte das Ergebnis am Ende aussehen:

Im nächsten Teil unserer kleinen Serie werden wir uns den XML Feed der Nasa zur Brust nehmen, diesen - mittels nativer Objective C Unterstützung - in JSON bringen und dann unsere ListView damit bestücken. Freut Euch also auch auf ein bisschen Objective C Code.

React Native - Erste Schritte

Man hört viel über React und damit in Verbindung: React Native. Doch was genau ist eigentlich React Native? Kann ich damit auch mehr als "einfache" Projekte realisieren? Diese Frage möchte ich in meiner kleinen React Native Reihe versuchen zu beantworten.

Heute soll es uns aber lediglich um die Installation der benötigten Tools gehen und wie ich ein überhaupt ein React Native Projekt erstelle.

Was ist React?

React ist eine von Facebook entwickelte, open source Javascript Bibliothek. Grundgedanke bei der Entwicklung der Bibliothek war, große datenintensive Anwendungen zu entwicklen und diese in direkt besser wartbar zu gestalten.
Facebook selbst sagt das React kein MVC Framework ist und auch keines sein möchte. Ok.
Größter Unterschied zu den vielen anderen Javascript Frameworks: Wir haben keine Templates. Präsentationslogik sowie das Markup werden in sogenannten Components zusammengelegt, dass bringt den Vorteil der Wiederverwertbarkeit mit sich. Wer jetzt String-Konkatenation-Horror-Gebilde befürchtet, wie:

'<a href="' + data.url + '">' + data.link + '</a>'  

den kann ich beruhigen. Mit React ist es möglich das gleiche so darzustellen:

<a href="{data.url}">{data.link}</a>  

Besonders wichtig zu erwähnen ist: Ändern sich die zugrundeliegenden Daten/Entitäten in einer React Anwendung, so rendert die Bibliothek automatisch den entsprechenden Teil der DOM neu. Änderungen werden somit sofort sichtbar. Um dies performant zu gestalten baut sich die Bibliothek eine virtuelle DOM und ermittelt über diffs, welchen Teil des DOMs es neu-rendern muss.

Wer mehr über React erfahren möchte darf sich die schöne ausführliche Dokumentation durchlesen.

Gründe React einzusetzen

  • Performance - React ist schnell. Es wurde mit dem Hintergedanken entwickelt performant und effektiv zu sein. Um lange GC Zyklen zu vermeiden werden Objekte gepoolt und somit wird dem JIT auch der Boden unter den Füßen weggezogen. React liest und schreibt in Batches in die DOM, der Vorteil liegt hier auf der Hand: Weniger Layoutzyklen im Browser = bessere Performance. Der Fakt der virtuellen DOM trägt zur Performance natürlich bei.
  • Compositon over Inheritance - Dieses Mantra wird von React gelebt. Wir sollen in einzelnen Komponenten denken, leben und atmen. Dies ermöglicht einen modularen Code, was uns Spaghetti Bolognese Eskapaden erspart und den Entwickler dazu zwingt über das, was er dort fabriziert nochmal näher nachzudenken. Erfüllt meine Komponente eine Aufgabe? Oder doch gleich n? Natürlich kann man auch in Vanille JS modularen Code erzeugen.
  • Es ist anders. Wir sind Entwickler, wir lieben neue Technologien/Entwurfsmuster/Frameworks/Kaffeesorten. Es versucht viele Dinge anders zu lösen als die bisherigen JS Frameworks/Bibliotheken. Es ist eben nicht ein typische MVC wovon wir ca. - allein im JS Umfeld - drölfmilliarden haben.
  • Durch seine Struktur hilft es gerade bei größeren Projekten die Übersicht zu wahren. Punkt.

React Native

Mit React Native möchte man den Entwicklern die Möglichkeit geben native Apps - hier sind vor allem die mobilen Plattformen (iOS, Android) gemeint - in einer bereits bekannten Sprache (JavaScript) und Bibliothek (React) zu erstellen. Stand jetzt: iOS only. Facebook arbeitet mit Hochdruck dran, auch Android Apps mit React Native zu realisieren. Write once, deploy many. So könnte man sagen.

Um dies zu realisieren bietet uns die Bibliothek so genannten native Komponenten. Die gängigsten iOS Klassen haben bereits eine React Native Komponente.

Das besonderes jedoch bei dem Ganzen: Man kann eine entsprechende Anwendung ohne weiteres mit nativem Code erweitern. Klingt jetzt erstmal paradox, wegen konsistent und sowas, kann aber im Ernstfall sehr hilfreich werden. Zur Dokumentation gehts hier lang.

Vorraussetzungen / Installation

  1. ein Mac mit Xcode (ab 6.3)
  2. Homebrew - beliebter Paketmanager für Mac OS X. Natürlich könnt Ihr node/npm und die restlichen Pakete händisch installieren. Ich bevorzuge hier den bequemen weg.
  3. Node bzw. npm
brew install node  
  1. Watchman - Wachmann ist ebenfalls ein Facebook Open Source Projekt. Und überwacht unsere Dateien, stellt Änderungen fest und führt dann entsprechen - in unserem Fall - einen Build aus.
brew install watchman  
  1. (optional) flow - Ich bin Javamensch. Statische Typen-Überprüfungen lassen mich besser schlafen.
brew install flow  

Attacke

Wir müssen zunächst einmal die react-native-cli installieren. Damit sind wir in der Lage über die Konsole ein entsprechendes Projekt zu initialisieren. Ich weiss, CLI klingt eigentlich nach mehr Power, aber gut.

npm install -g react-native-cli  

Jetzt fehlt nur noch ein Kommando und wir haben unser erstes React Native Projekt angelegt.

react-native init HelloWorldReactProject  

Das Ganze dauert einen Moment, beim ersten starten wird Node/npm noch das eine oder andere Paket installieren. Holt euch einen Kaffee/Tee.

Wir öffnen unser React Native Projekt jetzt einfach in Xcode

open HelloWorldReactProject/HelloWorldReactProject.xcodeproj  

Xcode begrüßt uns, wir erwidern den Gruß mit einem CMD+R. Et voila! Erste native App fertig.

Lasst euch bitte nicht irritieren:
Die index.ios.js Datei befindet sich nicht im Xcode Projekt, ihr findet Sie aber im Projekt Ordner wieder. Wer möchte fügt Sie einfach per Drag’n Drop dem Projekt hinzu oder nutzt hier für den "Add Files to Project" Dialog.

Im nächsten Artikel widmen wir uns der ListView und bauen einen einfachen RSS Reader.