React Native - Getting Started

Vous êtes developpeur web et avez des connaissances JavaScript et ReactJS ? Vous avez déjà entendu parler de React Native mais vous ne savez pas par où commencer ? Vous êtes au bon endroit !

React Native est un framework JavaScript permettant de créer des applications iOS et Android.

Dans cet article, je vais vous donner les outils et les quelques connaissances pour bien commencer.

Sommaire

Cet article sera divisé en 3 parties. Le concept, le choix de la plateforme et les bases pour bien commencer.

Tout d’abord: Qu’est ce que React Native ? Comment ça fonctionne ? Qu’est ce que ça fait?

Ensuite, le choix de la plateforme. Cela vous permettra de commencer à sortir du code plus ou moins vite :

Trois choix s’offrent à vous :

  • Snack : Pas le temps d’attendre, c’est parti !
  • Expo : Installer 2-3 dépendances et initialiser un projet n’est pas effrayant
  • React Native : Installer toutes les dépendances natives et coder sans surcouche

Les avantages, inconvénients et descriptions seront décrits plus bas.

Ensuite, on passera au code : comment fonctionne React Native, quelques exemples et comment faire un pokédex.

Concept

React Native a pour but de faciliter la création d’applications natives iOS et Android avec JavaScript.

Le principe est un peu différent de certains autres projets comme Cordova ou PhoneGap. Ceux-ci créaient une instance web dans l’application et utilisaient des plugins pour accéder aux API natives comme la caméra, la connexion Bluetooth ou encore le stockage. Dans un environnement React Native, vous allez composer votre application avec les APIs et composants JS qui vont être convertis en éléments et comportements natifs.

La partie JS va s’occuper de guider les éléments de vue, les requêtes systèmes et tout ce qui compose une application pour en construire une 100% native.

Concept

Comme son nom le suppose, ce framework est basé sur ReactJS. Et au lieu de gérer le DOM, comme le fait React, React Native va gérer les composants natifs. Tout ceci est fait via 2 composants très importants :

le JavaScriptCore et le RCTBridgeModule.

Le JSCore va éxécuter le JavaScript et le Bridge va faire la connection entre la partie native et le JS.

La Plateforme

Avant de parler des plateformes de développement, on va faire le point sur les simulateurs/émulateurs.

Avant d’en installer un, il faut installer les environnements :

  • Pour iOS, c’est XCode.
  • Pour Android, c’est le Android Studio.

Pour XCode, il ne faut pas aller bien loin. Il n’est disponible que sur le store Mac. Désolé pour les Linux ou Windows, il faudra rester sur Android. Les émulateurs pour chaque device sont directement disponible avec la dernière version d’iOS.

Pour Android Studio, vous allez devoir créer vos émulateurs.

Le site developer.android.com explique très bien ces étapes :

Snack

Snack va vous permettre de prendre React Native en main sans plus attendre.

Mise en place

Aucune!

La plateforme est disponible via https://snack.expo.io. Un exemple de projet s’affichera aléatoirement.

Cet éditeur en ligne est tout à fait adapté si vous souhaitez commencer à jouer avec React Native.

Avantages

  • Rien à installer
  • L’API React Native et Expo disponible
  • Test en live sur un émulateur iOS ou Android dans le navigateur ou sur votre téléphone
  • Ajout de librairies JS tiers
  • Exportable en projet Expo

Inconvénients

  • Nécessite un compte Expo pour certaines fonctionnalités comme la sauvegarde
  • Dépendant d’Expo
  • File d’attente pour le test sur émulateur
  • Impossible d’ajouter des dépendances natives
  • Impossible hors-ligne

Expo

Expo constitue un ensemble de librairies, de services et d’outils tournant autour de React Native. On va notamment retrouver expo-cli qui permet de générer un projet React Native avec la surcouche Expo.

Avec Expo, vous allez pouvoir créer et déployer une application iOS et Android sans voir la couche native. Cependant, même s’il est spacieux, vous restez dans l’enclos prévu par Expo. Les versions de React Native sont fixées dans les versions d’Expo et les modules et corrections natifs sont limités (Librairies natives non disponibles, celles qui sont disponibles doivent être mises à jour en même temps qu’Expo,…).

Mise en place

Dépendances

  • NodeJS (version > 8.3)
  • Un Package Manager (ici Yarn v1.12.0)
  • (Optionnel mais recommandé)Watchman (v4.9.0) pour OSX uniquement

Pour les tests sur émulateurs, il faut bien sûr ajouter les dépendances associées aux plateformes :

Environnement

Pour initialiser le projet, il suffit d’exécuter les commandes suivantes :

# Ajout de Expo CLI en global
$ yarn global add expo-cli

# Initialisation du projet
$ expo init NomDuProjet

# Voilà, une fois le projet généré, il faut se déplacer dans le projet
$ cd NomDuProjet

# et le démarrer
$ yarn start

Un serveur Metro Bundler se lancera et un interface web s’ouvrira. Pour tester l’application 2 solutions :

  • Votre device
  • Simulateur (Nécessite plus de dépendances)

Cette solution est intéressante pour des petits projets. Certaines contraintes sont à prendre en compte. Le Bluetooth ou encore les achats In-App ne sont pas supporté.

Avantages

  • Peu de dépendances
  • L’API React Native et Expo disponibles
  • Test en live sur un émulateur iOS ou Android ou sur votre téléphone
  • Ajout de librairies JS tierces

Inconvénients

  • Nécessite un compte Expo pour certaines fonctionnalités comme la génération des binaires
  • Certaines fonctionnalités natives ne sont pas accessibles
  • Les dépendances React et React Native sont gérées par les versions d’Expo
  • Impossible d’ajouter des dépendances natives

React Native

Et nous voilà sur React Native en “standalone”. La première mise en place d’un projet React Native est longue.

Pourquoi?

Parce qu’il faut mettre en place tous les environnements natifs. Il faut aussi installer toutes leurs dépendances et configurer les émulateurs.

Cependant, vous avez le contrôle sur la partie native. Tous les packages natifs de la communauté sont à portée de main.

Mise en place

Dépendances

  • NodeJS (version > 8.3)
  • Un Package Manager (ici Yarn v1.12.0)
  • Watchman (v4.9.0) pour OSX uniquement

Pour Android :

Pour iOS:

Environnement

Pour initialiser le projet, il suffit d’exécuter les commandes suivantes:

# Ajout de React Native CLI en global
$ yarn global add react-native-cli

# Initialisation du projet
$ react-native init NomDuProjet

# Voilà, une fois le projet généré, il faut se déplacer dans le projet
$ cd NomDuProjet

# Et le démarrer

# Android + un émulateur doit déjà être ouvert sinon une erreur apparaitra
$ react-native run-android

# iOS
$ react-native run-ios

Un serveur Metro Bundler se lancera et l’application s’ouvrira dans votre émulateur.

Commencer un projet avec react-native-cli permet de partir sur une base solide avec toutes les possibiltés des applications natives à disposition.

Avantages

  • L’API React Native
  • Modifications natives disponibles
  • Test en live sur un émulateur iOS ou Android ou sur votre téléphone
  • Ajout de librairies JS et Native (iOS ou Android) tiers

Inconvénients

  • Complexe à mettre en place
  • Peut nécessiter des modification de la partie native

Le code

Primitives, Composants et API

Comme décrit plus haut, la création de l’application passera principalement par du JavaScript et des composants React. Mais ici, on peut oublier toutes les primitives du web. Les composants comme les <div/>, <span/> ou autres n’existent pas.

Il faut directement passer par le module react-native :

import { View } from "react-native";

Et c’est à partir de là qu’on va pouvoir construire notre premier composant.

import React, { Component } from "react";
import { Text, View } from "react-native";

class FirstComponent extends Component {
  render() {
    return (
      <View>
        <Text>Hello You!</Text>
      </View>
    );
  }
}

D’autres composants sont disponibles tel que <FlatList/>, <ScrollView/> ou encore <Image/>. Pour lister les plus communs :

  • <View/> : un conteneur semblable à une balise div en web
  • <Text/> : un conteneur indispensable pour le texte
  • <ScrollView/> : car une “vue” native n’est pas scrollable par défaut
  • <FlatList/> : composant permettant d’afficher une liste d’éléments en fonction de données avec de meilleurs performances qu’une liste classique
  • <TouchableOpacity/> : un conteneur semblable à une balise <button/> en web
  • <TextInput/> : l’équivalent d’une balise web <input/> de type text

Mais react-native ne met pas seulement à disposition des composants “classiques”. Classique dans le sens où le rendu et le comportement seront les mêmes sur iOS et Android.

D’autres composants comme ActivityIndicator, Button, Slider ou encore Switch ont un rendu différent en fonction de la plateforme.

import React from "react";
import { Button } from "react-native";

const NativeButton = () => (
  <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
    <ActivityIndicator />
    <Button title="Button" />
    <Switch />
  </View>
);

Native Components iOSNative Components Android

En plus d’une mise à disposition de composant, on va retrouver une liste d’API et d’helpers pour pouvoir s’adapter au mieux à cet environement. Les plus communs et utilisés sont :

  • StyleSheet : Permet de créer des objets de style
  • Platform : Accède aux informations sur l’OS utilisé
  • Dimensions : Permet de récupérer les dimensions de l’écran utilisé

Mais d’autres existent, comme : CameraRoll, ClipBoard, Share ou encore Keyboard.

Un exemple de l’API Share :

import React from "react";
import { Button, Share } from "react-native";

const ShareHelloButton = () => {
  const shareHello = () => {
    Share.share({ message: "Hello you" });
  };

  return <Button title="Button" onPress={shareHello} />;
};

Native Share iOSNative Share iOS

Encore plus spécifique, certains composants ne fonctionnent que sur une plateforme. Ces composants sont généralement identifiés avec l’OS dans le nom du composant, comme : DatePickerIOS, PermissionsAndroid ou ActionSheetIOS.

Chaque plateforme est différente et il faudra parfois séparer les comportements. Plusieurs mécanismes existent pour cela.

Plateforme cible

Il existe deux moyens pour différencier le comportement en fonction de la plateforme.

L’API ‘Platform’

Comme dit plus haut, l’API Platform est accessible depuis la librairie react-native et permet de recupérer des informations spécifiques à l’OS.

On peut donc l’utiliser comme suit :

import { Platform } from "react-native";
const isIOS = Platform.OS === "ios";

Mais on peut aussi sélectionner un object spécifique grâce à la fonction select :

import React from "react";
import { Platform, View, Text } from "react-native";

const infoByPlatform = Platform.select({
  ios: {
    title: "Hello from iOS",
    body: "Please eat apple"
  },
  android: {
    title: "Hello from iOS",
    body: "Please play with droid"
  }
});

export const HelloByPlatform = () => (
  <View>
    <Text>{infoByPlatform.title}</Text>
    <Text>{infoByPlatform.body}</Text>
  </View>
);

Extension de fichier

Le compilateur se sert des extensions spécifiques pour différencier les OS :

  • *.android.js
  • *.ios.js

Vous pouvez par exemple créer un composant TestComponent dans un fichier TestComponent.ios.js et un autre différent dans TestComponent.android.js.

Cela restera transparent pour le reste de l’application car si les 2 fichiers sont dans le même dossier, on peut importer le composant normalement.

Reprenons le composant plus haut :

// HelloByPlatform.ios.js
import React from "react";
import { View, Text } from "react-native";

export const HelloByPlatform = () => (
  <View>
    <Text>"Hello from iOS"</Text>
    <Text>"Please eat Apple"</Text>
  </View>
);
// HelloByPlatform.android.js
import React from "react";
import { View, Text } from "react-native";

export const HelloByPlatform = () => (
  <View>
    <Text>"Hello from Android"</Text>
    <Text>"Please play with droid"</Text>
  </View>
);

On pourra importer le composant comme suit dans un autre fichier :

import HelloByPlatform from "./HelloByPlatform";

Styles et Dimensions

Pour mettre en forme et harmoniser tous ces composants, chacun d’entre eux met à disposition une propriété “style”.

On va pouvoir lui fournir un objet de style avec des valeurs de style se rapprochant généralement du CSS. On va retrouver la plupart des valeurs de base en kebab-case (background-color), en camelCase (backgroundColor).

Il est préferable de créer ces objets de style avec le helper StyleSheet. Cela permet d’éviter un recalcul du style à chaque rendu.

Prenons l’exemple suivant :

import React, { Component } from "react";
import { View, StyleSheet } from "react-native";

export const StyleExample = () => (
  <View style={styles.container}>
    <View style={styles.firstChild} />
    <View style={styles.secondChild} />
  </View>
);

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    flexDirection: "row"
  },
  firstChild: {
    width: 50,
    height: 50,
    backgroundColor: "powderblue"
  },
  secondChild: {
    width: 100,
    height: 50,
    backgroundColor: "steelblue"
  }
});

Dans le premier objet de style, on retrouve les fonctionnalités flex. Attention cependant, la propriété flexDirection a pour valeur par défaut column contrairement à flex-direction en web qui a row.

  container: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center",
    flexDirection: "row"
  }

Ensuite, dans les 2 autres objets, on retrouve les propriétés de taille height et width. Ces propriétés sont SANS UNITÉ ! En effet, l’unité utilisée dans une application sera la même partout et représente un “pixel”.

  firstChild: {
    width: 50,
    height: 50,
    backgroundColor: "powderblue"
  }

Et justement en parlant de taille, on va pouvoir récupérer les dimensions de l’écran de l’appareil.

L’API Dimensions est là pour exposer l’objet “window” et ainsi pouvoir récupérer la hauteur et largeur.

import { Dimensions } from "react-native";
const { height, width } = Dimensions.get("window");

Mais aussi mettre en place un “EventListener” sur les changements de taille.

import { Dimensions } from "react-native";
const eventListenerDimensions = Dimensions.addEventListener("change", function({
  height,
  width
}) {
  console.log("New height: " + height + ", new width: " + width);
});

En effet, les dimensions changent lors de certains évènements ; lorsque l’appareil change d’orientation, par exemple.

Debug

Passons maintenant à la partie debug. React Native embarque quelques puissants outils. Mais le plus important reste le menu de debug.

Pour ouvrir le menu debug, plusieurs possibilités :

  • Émulateur iOS : Cmd + D
  • Émulateur Android : Cmd|Ctrl + M
  • Appareil physique : secouer l’appareil

Il se présente sous cette forme :

Developer Menu

Les éléments les plus intéressants sont :

  • Reload : Recharger l’application pour appliquer les modifications apportées au JS
  • Enable Live Reload : Recharger l’application à chaque modification apportée au JS
  • Show Inspector : Affiche un menu qui permet d’activer et désactiver l’inspecteur. Celui-ci permet d’examiner les composants selectionnés.
  • Start/Stop Remote JS Debugging : Active/Désactive le debug de l’application par un autre outils

L’outil par défaut qui s’ouvrira sera votre navigateur web avec l’addresse http://localhost:8081/debugger-ui. Vous pourrez donc utilisez la console (erreur, warning, log,…) et le debugger avec les points d’arrêt debugger;.

Tips: La fonction console.warn appelle le composant YellowBox qui affiche une modal sur votre application. Et la fonction console.error affiche un modal plus grande avec la stack.

Mais le top des outils de debug pour React Native (c’est mon avis), n’est autre que React Native Debugger.

Car avec cet outil vous avez à disposition :

  • Le Redux Developer tools
  • Le React Inspector qui sera directement lié à l’inspecteur React Native
  • Les Chrome Developer tools avec la console, le debugger et les differents inspecteur (réseau, performance,…)

React Native Debugger

Première application pas à pas

Eh bien maintenant il faut mettre tout ça en pratique.

Dans ce pas à pas, nous allons faire un Pokédex. Cela nous permettra de passer en revue la plupart des notions abordées plus haut.

App QR Code App

Initialisation

Peut importe la plateforme choisie au début (Snack, Expo, React Native), tout se passera dans le fichier App.js qui se trouve à la racine.

On va donc commencer par remplacer la totalité du contenu pour partir de la même base :

import React, { Component } from "react";
import { View } from "react-native";

export default class App extends Component {
  render() {
    return <View />;
  }
}

La liste

Pour commencer, on va donc utiliser le composant React Native optimisé pour les listes : <FlatList/>.

Ce composant nécessite deux propiétés :

  • data : Un tableau avec les données de chaque élément
  • renderItem : Une fonction retournant un composant avec les données

N’oubliez pas: pas de primitives. Il faut donc importer tous les composants. Je vous le dis maintenant mais il faudra le faire pour chaque nouvel élément que nous rencontrerons.

import { FlatList, View, Text } from "react-native";

On va créer des données factices,

state = {
  pokedex: [{ name: "Pokemon1" }, { name: "Pokemon2" }, { name: "Pokemon3" }]
};

la fonction qui affichera nos items,

renderPokemonItem = function({ item }) {
  return (
    <View>
      <Text>{item.name}</Text>
    </View>
  );
};

et lier tout ça.

return (
  <FlatList data={this.state.pokedex} renderItem={this.renderPokemonItem} />
);

Pour de meilleurs performances, il faudrait implémenter la propriété keyExtractor. Cette propriété permet, via une fonction, de renvoyer une clé unique pour chaque composant de la liste. On fera ça plus tard.

Connexion à l’API

On va remplir notre Pokédex avec des Pokémons! Pour cela, un appel à la fameuse PokéAPI. Attention, cette API est gratuite et restreint l’utilisation à 100 requêtes par minute par IP. C’est pour cela que nous allons mettre en cache les données.

On va donc utiliser AsyncStorage. Attention, cette API sera bientôt retirée de react-native et devra être ajoutée via le package react-native-community/react-native-async-storage. Pour plus de simplicité, on l’utilisera via react-native.

On va donc créer les fonctions de gestion du store :

saveData = async (key, data) => {
  try {
    // Transformation des données en string
    const stringData = JSON.stringify(data);
    // Stockage de la donnée via un clé
    await AsyncStorage.setItem(key, data);
    // Si on veut être sûr que la donnée on peut valider en retournant un bool
    return true;
  } catch (error) {
    return false;
  }
};

getData = async key => {
  try {
    // On recupère la donnée avec la clé
    const value = await AsyncStorage.getItem(key);
    // Si quelque chose est retourné on le parse
    if (value) {
      return JSON.parse(value);
    }
    // Si aucune valeur n'est trouvée, value === null
    return value;
  } catch (error) {
    return null;
  }
};

Tout d’abord, l’appel à l’API :

  state = {
    pokedex: []
  };

  componentDidMount() {
    this.fetchPokedex();
  }

  fetchPokedex = async () => {
    let pokedex = await this.getData('pokedex');
    if (!pokedex || !pokedex.length) {
      const response = await fetch(
        `https://pokeapi.co/api/v2/pokedex/1/`
      );
      const data = await response.json();
      pokedex = data.pokemon_entries;
      await this.saveData('pokedex', pokedex);
    }
    this.setState({ pokedex });
  };

On va adapter le reste du code aux nouvelles données.

renderPokemonItem = ({ item }) => (
  <View style={styles.itemContainer}>
    <Text style={styles.itemLabel}>{item.pokemon_species.name}</Text>
  </View>
);

Ajouter le keyExtractor maintenant qu’on a un id unique.

keyExtractor = item => item.entry_number.toString();

Mettre en place une vue lorsque l’appel est en cours. Ça tombe bien! Dans react-native on a un spinner natif : ActivityIndicator.

renderEmptyPlaceholder = () => (
  <View>
    <ActivityIndicator size="large" />
  </View>
);

Et on met à jour le composant FlatList.

<FlatList
  data={this.state.pokedex}
  renderItem={this.renderPokemonItem}
  keyExtractor={this.keyExtractor}
  ListEmptyComponent={this.renderEmptyPlaceholder}
/>

On a notre liste de pokémon. Mais ce n’est pas très esthétique.

Un peu de style

Pour le moment, la liste est vraiment basique. On va ajouter un peu de style à tout ça.

On va donc créer les styles avec StyleSheet.

const styles = StyleSheet.create({
  list: {
    flex: 1
  },
  itemContainer: {
    flex: 1,
    padding: 10,
    alignItems: "center",
    justifyContent: "center"
  },
  itemLabel: {
    fontSize: 20
  },
  emptyComponentContainer: {
    flex: 1,
    alignItems: "center",
    justifyContent: "center"
  }
});

Et ajouter les styles aux composants :

<View style={styles.itemContainer}>
  <Text style={styles.itemLabel}>{item.pokemon_species.name}</Text>
</View>
renderEmptyPlaceholder = () => (
  <View style={styles.emptyComponentContainer}>
    <ActivityIndicator size="large" />
  </View>
);
<FlatList
  style={styles.list}
  data={this.state.pokedex}
  renderItem={this.renderPokemonItem}
  keyExtractor={this.keyExtractor}
  ListEmptyComponent={this.emptyComponentContainer}
/>

Et on va en profiter pour supprimer la barre de status pour plus d’immersion. Avec le composant StatusBar, c’est très simple.

On va ajouter un conteneur Fragment (en raccourci <></>), et y mettre nos composants.

return (
  <>
    <StatusBar hidden />
    <FlatList
      style={styles.list}
      data={this.state.pokedex}
      renderItem={this.renderPokemonItem}
      keyExtractor={this.keyExtractor}
      ListEmptyComponent={this.emptyComponentContainer}
    />
  </>
);

Vous pouvez aussi changer sa couleur, son style et l’animer comme bon vous semble.

C’est déjà mieux! Mais je vous laisse le soin d’améliorer ça par la suite maintenant que vous savez comment faire.

Details et finition

Bon, on a une liste, il nous faut un peu de fonctionnalités. On va ajouter du détail. Pour cela, on va afficher une modal. Il va donc falloir :

  • Rendre les éléments de la liste cliquable
  • Afficher la modale et configurer la possibililté de retour
  • Recupérer les détails des Pokémons via l’API
  • Les stocker en local
  • Mettre du style

Pour la modale, vous utiliserez l’élément Modal. Et TouchableOpacity pour la possibilité de cliquer sur les items.

Voilà!

Je vous laisse ma version finale ici : https://snack.expo.io/@fromwood/react-native-pokedex

Mais ce n’est pas fini ! Il reste plein de choses à implémenter. La recherche, le thème et le style de l’application ou encore le choix du Pokédex sont des sujets à explorer.

Conclusion

React Native est une librairie très puissante qui offre beaucoup de possibilités. Comme énoncé plus haut, si vous souhaitez découvrir comment cela fonctionne, commencez avec Snack. La partie ‘Pas à pas’ est tout à fait réalisable et améliorable sur cette plateforme.

D’ailleurs, n’hésitez pas à nous partager votre Pokédex via twitter avec @rednetio. Celui-ci n’est qu’une esquisse et n’embarque que peu de fonctionnalités. Nous serons ravi de voir jusqu’où vous pouvez aller.

Librairies

Voici quelques librairies incontournables que je recommande :

Références & Documentation