Mettre en place une BottomBar/TabBar avec React-Native-Navigation et React-Native-Vector-Icons

Cet article va vous permettre de créer une application, et de mettre en place React-Native-Navigation pour la gestion de la navigation dans l’app. Nous mettrons également en place React-Native-Vector-Icons pour la gestion des icônes de la BottomBar/TabBar.

Lorsque j’ai créé mes premières applications en React Native, j’ai utilisé la librairie React-Navigation, qui offre une grande flexibilité. En tant que développeur natif iOS, je n’étais pas complètement satisfait du comportement de cette librairie. Après quelques recherches, j’ai découvert React-Native-Navigation édité par Wix, qui fait appel à la navigation native d’iOS et d’Android, et qui m’a beaucoup plus convaincue dans son utilisation pour les applications que je devais créer.

Pour cet article, nous allons devoir agir sur l’ensemble de l’application, et non juste sur un composant. Je vais donc décrire l’ensemble du parcours, de la création de l’app jusqu’à la mise en place de la BottomBar/TabBar (nous utiliserons TabBar dans le reste de l’article).

Initialisation de l’app

Commençons par créer notre application. Je vais nommer mon app OnTheBeachDemo, qui aura pour but de travailler sur le code de cet article, mais également de stocker les démos que je crée pour mes autres articles. Nous allons créer l’application, puis nous la lancerons directement pour s’assurer que tout fonctionne correctement.

react-native init OnTheBeachDemo
cd OnTheBeachDemo/
react-native run-ios
react-native run-android

Comme vous pouvez le voir, je lance l’app sur les 2 environnements. Pour la mise en place de cette TabBar, je vais travailler à chaque étape sur les 2 environnements, qui ont chacun leurs spécificités en matière de navigation, et donc ne rien laisser de côté.

L’app par défaut se charge sur iOS et Android sans problème

Mise en place de React-Native-Navigation

La mise en place de React-Native-Navigation change totalement la manière dont l’application va s’initialiser. On va modifier l’index.js de React Native, l’AppDelegate d’iOS, le MainApplication et le MainActivity d’Android.

Les étapes d’installation de React-Native-Navigation sont très bien décrites dans la documentation, mais je vais quand même décrire le processus ci-dessous, pour avoir un tuto de A à Z dans une seule page.

Commençons par ajouter React-Native-Navigation à notre projet.

yarn add react-native-navigation

Tout d’abord nous allons installer React-Native-Navigation ‘nativement’.

cd ios/ && pod install && cd ..

Maintenant, nous allons configurer le code natif, en modifiant le fichier AppDelegate.m

open ios/OnTheBeachDemo/AppDelegate.m

Nous allons supprimer l’intégralité du code présent dans cette page et le remplacer par le code ci-dessous.

#import “AppDelegate.h”#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <ReactNativeNavigation/ReactNativeNavigation.h>
@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
#if DEBUG
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@”index” fallbackResource:nil];
#else
jsCodeLocation = [[NSBundle mainBundle] URLForResource:@”main” withExtension:@”jsbundle”];
#endif
[ReactNativeNavigation bootstrap:jsCodeLocation launchOptions:launchOptions]; return YES;
}
@end

Avant de passer à la configuration sur Android, nous allons vérifier que tout est ok sur iOS. Et pour cela nous devons d’abord configurer notre app React Native.

Pour utiliser React-Native-Navigation, nous allons devoir modifier le fichier index.js situé à la racine du projet. Nous allons supprimer l’ensemble du code pour la remplacer par le code ci-dessous.

import { Navigation } from ‘react-native-navigation’;
import App from ‘./App’;
Navigation.registerComponent(‘fr.onthebeachdemo.app’, () => App);Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
component: {
name: ‘fr.onthebeachdemo.app’
}
}
});
});

Le code ci-dessus est la manière la plus simple de lancer une application avec React-Native-Navigation. Pour pouvoir afficher un écran dans l’app via React-Native-Navigation, celui-ci doit être préalablement déclaré. C’est le role de la ligne

Navigation.registerComponent(‘fr.onthebeachdemo.app’, () => App);

Nous déclarons que l’écran contenu dans le fichier App.js a comme id (screenID) fr.onthebeachdemo.app. Dès que nous aurons besoin d’afficher cet écran dans l’app, nous pourrons le faire grâce à ce screenID.

Ensuite via Navigation.events().registerAppLaunchedListener(), nous allons initier notre application, et déclarer le premier écran.

Lançons maintenant notre projet sur iOS pour nous assurer que tout fonctionne correctement, en le compilant à nouveau via le terminal.

react-native run-ios

Si tout fonctionne bien on va retrouver l’application par défaut de React Native. Nous améliorerons le code de notre index plus tard.

La configuration d’Android va nous prendre un peu plus de temps.

Commençons par mettre à jour le fichier /android/build.gradle. Je vais faire simple et efficace, on va supprimer tout le code de ce fichier et le remplacer par celui ci-dessous.

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = “28.0.3”
minSdkVersion = 19
compileSdkVersion = 28
targetSdkVersion = 28
RNNKotlinVersion = “1.3.61”
RNNKotlinStdlib = “kotlin-stdlib-jdk8”
}
repositories {
google()
jcenter()
mavenLocal()
mavenCentral()
}
dependencies {
classpath “org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61”
classpath ‘com.android.tools.build:gradle:3.5.3’
}
}
allprojects {
repositories {
google()
mavenLocal()
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url(“$rootDir/../node_modules/react-native/android”)
}
maven {
// Android JSC is installed from npm
url(“$rootDir/../node_modules/jsc-android/dist”)
}
maven { url ‘https://jitpack.io' }
}
}

Maintenant, nous allons modifier /android/app/src/main/java/com/onthebeachdemo/MainActivity.java. Là encore, on supprime tout et on remplace par le code ci-dessous.

package com.onthebeachdemo;import com.reactnativenavigation.NavigationActivity;public class MainActivity extends NavigationActivity {}

Passons à la mise à jour de /android/app/src/main/java/com/onthebeachdemo/MainApplication.java. La modification du code est moins ‘radicale’ pour ce fichier.

À la fin de la liste des import, nous allons ajouter ces trois lignes :

import com.reactnativenavigation.BuildConfig;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.react.NavigationReactNativeHost;

Nous remplaçons la ligne ci-dessous

public class MainApplication extends Application implements ReactApplication {

par

public class MainApplication extends NavigationApplication {

Puis remplaçons

new ReactNativeHost(this)

par

new NavigationReactNativeHost(this)

Et enfin supprimons la ligne ci-dessous

SoLoader.init(this, /* native exopackage */ false);

Pour nous assurer que tout fonctionne bien, nous pouvons à nouveau compiler l’application Android via le terminal.

react-native run-android

Encore une fois, vous devriez retrouver l’application par défaut de React Native.

Amélioration de notre app

Avant de commencer à travailler sur la TabBar, nous allons améliorer quelques éléments de notre application, pour travailler plus facilement et plus proprement.

Comme nous l’avons vu précédemment, nous devons déclarer nos écrans avant de pouvoir les intégrer dans notre navigation. Chaque écran a un screenID associé. Dans la suite de l’app, nous allons créer plusieurs écrans, et donc faire appel à plusieurs screenID, qu’il aura fallu préalablement déclarer. Nous allons donc :

  • Créer un fichier constants.js dans lequel nous allons déclarer tous les screenIDs que nous allons utiliser.
  • Créer un fichier screens.js dans lequel nous allons déclarer nos écrans.

Je vais aussi en profiter pour créer la structure globale de mon projet, en créant un dossier app à la racine de mon projet existant, à l’intérieur duquel je créerai un dossier components pour tous mes écrans, un dossier utils pour mes fichiers de configuration (notamment constants.js et screens.js) et un dossier assets, pour stocker les images utilisées dans mon app. J’aurai donc la hierarchie suivante :

android
app
|_assets
|_images
|_components
|_utils
|_constants.js
|_screens.js

Mon fichier constants.js va pour l’instant contenir la déclaration du screenID de mon écran App

//Déclaration des screenIDs
export const screenIDApp = ‘fr.onthebeachdemo.app’;

Mon fichier screens.js va me permettre de déclarer cet écran, en important son screenID depuis le fichier constants.js.

import { Navigation } from 'react-native-navigation';
import {
screenIDApp
} from ‘./constants’;
import App from ‘../../App’;export function registerScreens() {
Navigation.registerComponent(screenIDApp, () => App);
}

Enfin je vais modifier mon fichier index.js, pour enlever la déclaration de l’écran, et le remplacer par l’appel à la fonction registerScreens() de mon fichier screens.js. Sans oublier de remplacer l’appel au screenID de l’écran App par la constante screenIDApp.

import { Navigation } from ‘react-native-navigation’;
import {
screenIDApp
} from ‘./app/utils/constants’;
import { registerScreens } from ‘./app/utils/screens’;
//Déclaration des écrans
registerScreens();
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot({
root: {
component: {
name: screenIDApp
}
}
});
});

Maintenant que nous avons structurer notre projet, nous allons pouvoir commencer à créer des écrans, une TabBar, et à associer ces écrans à chaque élément de cette TabBar.

Mise en place de la TabBar de React-Native-Navigation

Lorsque nous avons initié la navigation de notre application avec Navigation.setRoot(), nous avons directement déclaré un écran. Cela signifie que l’application ne peut avoir qu’un seul écran sans navigation possible. Nous allons donc modifier cela.

Pour mettre en place une TabBar dans notre app, nous allons devoir remplacer la déclaration de notre root en remplaçant l’objet component par un objet bottomTabs.

L’objet bottomTabs est composé d’un objet children, contenant un enfant par section que nous voulons afficher. Les enfants peuvent contenir 2 types d’objet :

  • component : comme nous l’avons vu jusqu’à maintenant, à savoir une page sans navigation,
  • stack : création d’une navigation dans laquelle nous déclarons le premier écran à afficher.

Tous mes enfants seront des stacks afin de pouvoir facilement ajouter d’autres écrans à mon application. Une stack contient un objet children et un objet options. L’objet children est un tableau contenant un objet unique component, définissant le premier écran à afficher. Dans l’objet options, nous déclarerons un objet bottomTab, qui contiendra la définition de notre élément (nom, icône, couleurs..).

Pour notre exemple, nous allons créer une TabBar avec 3 entrées, qui pourraient correspondre aux 3 écrans suivants :

  • Démos : contenant la liste des écrans créés dans mes tutos pour en voir le résultat,
  • Articles : contenant la liste des mes articles pour pouvoir les consulter,
  • À propos : contenant les informations sur OnTheBeach.dev.

Commençons par créer les 3 écrans, puis nous créerons leur screenID, et les déclarerons comme écrans pour la navigation.

Dans le dossier components, je vais donc créer les fichiers demos.js, articles.js et about.js. Je mets ci-dessous le code de demos.js (qui pour l’instant affiche simplement le titre de la page, et pour les besoins de l’article, les fichiers articles.js et about.js seront exactement les mêmes avec leur titre respectif).

import React from ‘react’;
import { StyleSheet, View, Text } from ‘react-native’;
export default class Demos extends React.Component {
constructor(props) {
super(props);
this.state = {
}
}
componentDidMount() {
}
render() {
return (
<View style={styles.container}>
<Text>Demos</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: ‘center’,
justifyContent: ‘center’
}
});

J’ajoute les screenIDs de ces fichiers dans mon fichier constants.js, et je supprimer la déclaration d’App car je vais lancer mon app directement sur la TabBar, et cet écran n’a donc plus d’utilité.

//Déclaration des screenIDs
export const screenIDDemos = ‘fr.onthebeachdemo.demos’;
export const screenIDArticles = ‘fr.onthebeachdemo.articles’;
export const screenIDAbout = ‘fr.onthebeachdemo.about’;

Puis je mets à jour le fichier screens.js, en enlevant également la déclaration d’App.

import { Navigation } from ‘react-native-navigation’;
import {
screenIDDemos,
screenIDArticles,
screenIDAbout
} from ‘./constants’;
import Demos from ‘../components/demos’;
import Articles from ‘../components/articles’;
import About from ‘../components/about’;
export function registerScreens() {
Navigation.registerComponent(screenIDDemos, () => Demos);
Navigation.registerComponent(screenIDArticles, () => Articles);
Navigation.registerComponent(screenIDAbout, () => About);
}

Je peux maintenant supprimer le fichier App.js, et mettre à jour mon fichier index.js, pour déclarer la bottomTabs. Mais il me manque encore un élément important pour créer ma TabBar, à savoir les icônes définissant chaque entrée de la TabBar. Je fais donc faire une petite aparté pour mettre en place React-Native-Vector-Icons qui permet d’utiliser les polices d’image type FontAwesome ou MaterialIcons.

Ajoutons React-Native-Vector-Icons à notre projet, via le terminal.

yarn add react-native-vector-icons

Initialisation pour iOS

cd ios && pod install && cd ..

Je vais utiliser la police MaterialIcons pour le projet. Dans Xcode, je sélectionne l’onglet Info, pour ajouter une ligne dans le tableau Custom iOS Target Properties, avec comme clé Fonts provided by application, et pour l’Item 0 je lui donne la valeur MaterialIcons.ttf.

Initialisation pour Android

Dans le fichier android/app/build.gradle, nous allons ajouter les lignes suivantes.

project.ext.vectoricons = [
iconFontNames: [ 'MaterialIcons.ttf' ]
]
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"

Maintenant nous allons utiliser la police MaterialIcons pour les icônes de le TabBar. Avant de pouvoir utiliser une icône de la police dans la TabBar, il faut transformer celle-ci en image.

Dans notre fichier index.js, nous allons donc importer la police, et avant de déclarer la TabBar, nous allons créer les images nécessaires. Nous allons créer une méthode initIcons(), que nous allons appeler en amont de la création de la TabBar, et stocker les images créer dans le tableau iconsArray. Ensuite nous ajoutons la déclaration de la bottomTabs, en affectant les images du tableau pour chaque élément.

[…]
/**
* Gestion des icônes
*/
import IconFont from ‘react-native-vector-icons/MaterialIcons’;
const tabIconSize = 25;
let iconsArray = [];
initIcons = () => {
return new Promise(function (resolve, reject) {
Promise.all(
[
IconFont.getImageSource('code', tabIconSize, '#123456'),
IconFont.getImageSource('menu', tabIconSize, '#123456'),
IconFont.getImageSource('info', tabIconSize, '#123456')
]
).then((values) => {
iconsArray = values;
resolve(true);
}).catch((error) => {
resolve(false);
}).done();
});
};
[…]
Navigation.events().registerAppLaunchedListener(() => {
initIcons().then(() => {
Navigation.setRoot({
root: {
bottomTabs: {
children: [{
stack: {
children: [{
component: {
name: screenIDDemos
}
}],
options: {
bottomTab: {
text: 'Démos',
icon: iconsArray[0]
}
}
}
},
{
stack: {
children: [{
component: {
name: screenIDArticles
}
}],
options: {
bottomTab: {
text: 'Articles',
icon: iconsArray[1]
}
}
}
},
{
stack: {
children: [{
component: {
name: screenIDArticles
}
}],
options: {
bottomTab: {
text: 'Articles',
icon: iconsArray[1]
}
}
}
}]
}
}
});
});
});

Si on recompile les apps, via le terminal, on doit maintenant avoir une TabBar avec les icônes que nous avons choisies.

react-native run-ios
react-native run-android
Initialisation de la TabBar

Et voilà notre TabBar est mise en place.

Si vous souhaitez aller plus loin dans la gestion de la TabBar, vous pouvez jeter un coup d’oeil à mon article Créer une BottomBar/TabBar personnalisée en se basant sur React-Native-Navigation.

Show Runner de projets mobiles. Développeur React Native et passionné par les challenges du monde mobile.

Show Runner de projets mobiles. Développeur React Native et passionné par les challenges du monde mobile.