Création d’applications en utilisant la technologie prévue par le concepteur de l’OS.
Ces technologies sont incompatibles.
Nécessité de créer puis de faire évoluer une application pour chaque environnement.
Création d’applications mobiles compatibles avec plusieurs OS mobiles grâce à un framework dédié.
Une application hybride est une application web qui s’exécute à l’intérieur d’un navigateur.
Exemples : Apache Cordova, Ionic
Création d’applications mobiles compatibles avec plusieurs OS mobiles grâce à un framework dédié.
Le framework encapsule les véritables composants natifs de l’OS.
Exemples : React Native, Weex, Xamarin
Combine les avantages du natif (look’n’feel, performances) et de l’hybride (une seule base de code).
Limite potentielle : le support de l’OS dépend entièrement du framework.
Framework créé par Facebook, open source depuis 2015.
Déclinaison mobile du framework JavaScript React.
Framework pour faciliter la création et le déploiement d’applications React Native.
# Install expo-cli globally
# (Node.js and Git are prerequisites)
npm i -g expo-cli
# Create a new app named "appname" in its own subfolder
# Created files are automatically versioned into a Git repository
# The optional -t flag is used to select an Expo app template
# Run npx create-expo-app --template to see the list of available templates.
npx create-expo-app <appname> -t <expo-template>
# Alternative: expo init <appname> -t <expo-template>
Workflow managé : projet entièrement géré par Expo (plus simple).
Workflow bare : plus proche d’un projet React Native pur.
cd <appname> # Move into project directory
npm start # Or 'expo start'
Ensuite, scan du QR Code depuis l’application Expo Go (Android) ou l’appareil photo du smartphone (iOS).
L’application mobile Expo Go doit accéder au serveur web de la machine de développement pour pouvoir lancer l’application RN.
Il existe plusieurs modes de connexion :
{
"expo": {
"name": "HelloReactNative",
"slug": "HelloReactNative",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#FFFFFF"
}
},
"web": {
"favicon": "./assets/favicon.png"
}
}
}
{
"name": "helloreactnative",
"version": "1.0.0",
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"expo": "~47.0.9",
"expo-status-bar": "~1.4.2",
"react": "18.1.0",
"react-native": "0.70.5"
},
"devDependencies": {
"@babel/core": "^7.12.9"
},
"private": true
}
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
});
{...}
permettent d’inclure du code JavaScript dans le code JSX (plus de détails).// JSX for React (web)
const name = "Clarisse Agbegnenou";
const element = <p>Hello, {name}</p>;
// JSX for React Native (mobile)
const name = "Clarisse Agbegnenou";
const element = <Text>Hello, {name}</Text>;
Les composants sont les blocs de base d’une application React (Native).
Ils permettent de créer une UI sous forme déclarative par assemblage de composants inclus les uns dans les autres.
Ils doivent comporter une fonction qui définit leur rendu visuel.
Le fichier principal App.js
d’une application RN doit exporter un composant par défaut.
import React from 'react';
import { Text, View } from "react-native";
// HelloWorldApp is a function component
const HelloWorldApp = () => {
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text style={{ fontSize: 24 }}>Hello from a function component!</Text>
</View>
);
};
export default HelloWorldApp;
render()
définit leur rendu.import React, { Component } from "react";
import { Text, View } from "react-native";
// HelloWorldApp is a class component
class HelloWorldApp extends Component {
render() {
return (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
}}
>
<Text style={{ fontSize: 24 }}>Hello from a class component!</Text>
</View>
);
}
}
export default HelloWorldApp;
Implémentés de manière native par RN sous Android et iOS afin d’obtenir un look’n’feel et des performances optimaux.
style
disponible pour les composants de base.StyleSheet.create
.import React from "react";
import { StyleSheet, Text, View } from "react-native";
export default App = () => {
return (
<View style={styles.container}>
<Text style={styles.red}>just red</Text>
<Text style={styles.bigblue}>just bigblue</Text>
{/* Using an array of styles: last element has precedence */}
<Text style={[styles.bigblue, styles.red]}>bigblue, then red</Text>
<Text style={[styles.red, styles.bigblue]}>red, then bigblue</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
bigblue: {
color: "blue",
fontWeight: "bold",
fontSize: 30,
},
red: {
color: "red",
},
});
Ils permettent de créer une application par assemblage de composants élémentaires.
import React from "react";
import { StyleSheet, Text, View } from "react-native";
const Cat = () => {
return <Text>I am a cat!</Text>;
};
export default App = () => {
return (
<View style={styles.container}>
<Text>Welcome!</Text>
<Cat />
<Cat />
<Cat />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
});
import React from "react";
import { StyleSheet, Text, View } from "react-native";
// This component has two props: "name" and "age"
const Cat = ({ name, age }) => {
return (
<Text>
I am {name} the {age} years old cat!
</Text>
);
};
export default App = () => {
return (
<View style={styles.container}>
<Text>Welcome!</Text>
{/* We define the values of the props for each cat */}
<Cat name="Madchat" age="5" />
<Cat name="Félicette" age="3" />
<Cat name="Fritz" age="7" />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
});
const Cat1 = (props) => {
const name = props.name;
const age = props.age;
// ...
};
const Cat2 = ({ name, age }) => {
// ...
};
const [<getter>, <setter>] = useState(<initialValue>);
import React, { useState } from "react";
import { StyleSheet, Button, Text, View } from "react-native";
const Counter = () => {
// Add a variable named "count" to the component state.
// Its initial value is zero.
// The setCount function is used to update its value.
const [count, setCount] = useState(0);
return (
// Adjacent JSX elements must be wrapped in an enclosing tag.
// Fragments <> and </> let you do that without using an unnecessary wrapping element like View.
<>
<Button
title="Click me!"
onPress={() => {
// Update the state variable
setCount(count + 1);
}}
></Button>
{/* Show the current value of the state variable */}
<Text>You clicked {count} times</Text>
</>
);
};
export default App = () => {
return (
<View style={styles.container}>
<Counter />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
});
Les composants RN suivent un cycle de vie bien défini composé d’étapes : montage, rendu, mise à jour, démontage et suppression.
Deux possibilités pour définir la taille des composants :
Utile pour les composants qui doivent toujours être affichés à la même taille.
import React from "react";
import { View } from "react-native";
export default App = () => {
return (
<>
<View style={{ width: 50, height: 50, backgroundColor: "powderblue" }} />
<View style={{ width: 100, height: 100, backgroundColor: "skyblue" }} />
<View style={{ width: 150, height: 150, backgroundColor: "steelblue" }} />
</>
);
};
flex:1
=> le composant prend tout l’espace disponible, partagé équitablement entre les autres composants d’un même parent.
Une valeur de flex
plus élevée donne plus d’espace à un composant par rapport aux autres.
import React from "react";
import { View } from "react-native";
export default App = () => {
return (
// Try removing the `flex: 1` on the parent View.
// The parent will not have dimensions, so the children can't expand.
// What if you add `height: 300` instead of `flex: 1`?
<View style={{ flex: 1 }}>
{/* Try changing the flex values to see how children views share the screen space */}
<View style={{ flex: 1, backgroundColor: "powderblue" }} />
<View style={{ flex: 2, backgroundColor: "skyblue" }} />
<View style={{ flex: 3, backgroundColor: "steelblue" }} />
</View>
);
};
flexDirection
: flux des éléments
column
(par défaut), row
, column-reverse
, row-reverse
.
justifyContent
: axe principal
alignItems
: axe secondaire
import React from "react";
import { View, StyleSheet } from "react-native";
export default App = () => {
return (
<View style={styles.container}>
{/* Individual styles can be combined into an array */}
{/* Thus, common style properties can be factorized */}
<View style={[styles.box, styles.box1]} />
<View style={[styles.box, styles.box2]} />
<View style={[styles.box, styles.box3]} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
// Used to take into account the phone status bar at the top of the screen
// marginTop: 48,
justifyContent: "space-around",
alignItems: "center",
},
box: {
width: 100,
height: 100,
},
box1: {
backgroundColor: "powderblue",
},
box2: {
backgroundColor: "skyblue",
},
box3: {
backgroundColor: "steelblue",
},
});
Le composant TextInput permet la saisie de texte.
import React, { useState } from "react";
import { StyleSheet, Text, View, TextInput } from "react-native";
export default App = () => {
// Add character count to state
const [charCount, setCharCount] = useState(0);
return (
<View style={styles.container}>
<TextInput
style={styles.text}
placeholder="Enter some text"
onChangeText={(text) => {
// Update character count after input changes
setCharCount(text.length);
}}
/>
<Text>Character count: {charCount}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
text: {
fontSize: 22,
paddingBottom: 10,
},
});
Props = caractéristiques définies au moment de la création du composant.
Les props d’un composant sont modifiables uniquement par son composant parent.
Etat (state) = ensemble des données susceptibles d’être modifiées pendant l’exécution de l’application.
Chaque composant fonction React Native possède un état interne, géré via le hook useState
.
Toute modification de l’état déclenche un nouveau rendu du composant.
La gestion locale de l’état devient insuffisante lorsqu’un composant doit accéder à ou modifier l’état d’un autre composant.
Nécessité de partager un état commun entre certains composants.
L’état (température et échelle) commun aux deux composants de saisie est remonté dans App
, leur parent commun.
import React, { useState } from "react";
import { StyleSheet, View, Text, TextInput } from "react-native";
// Scale names used for display
const scaleNames = { c: "Celsius", f: "Fahrenheit" };
// Celsius/Fahrenheit conversion functions
function toCelsius(fahrenheit) {
return ((fahrenheit - 32) * 5) / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9) / 5 + 32;
}
// Component displaying if the water would boil or not, depending on the temperature
// We choose the Celsius scale for easier comparison with the boiling temperature
const BoilingResult = ({ tempCelsius }) => {
let message = "";
if (!Number.isNaN(tempCelsius)) {
message =
tempCelsius >= 100 ? "The water would boil" : "The water would not boil";
}
return <Text style={styles.text}>{message}</Text>;
};
// Component for displaying and inputting a temperature in a specific scale
const TemperatureInput = ({ value, scale, onChange }) => {
// Accessing scaleNames properties through bracket notation
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors
const placeholder = `Enter temperature in ${scaleNames[scale]}`;
return (
<TextInput
style={styles.text}
placeholder={placeholder}
onChangeText={(text) => {
// Call callback passed as component prop when input text changes
onChange(text);
}}
value={value}
/>
);
};
// Convert a temperature using a given conversion function
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return "";
}
// Call the conversion function on input
const output = convert(input);
// Keep the output rounded to the third decimal place
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
// Main component
export default App = () => {
// Common state is lifted here because this component is the closest parent of TemperatureInput components.
// We store only the most recently changed input with its scale.
// Temperature is stored as a string to handle missing values.
const [temperature, setTemperature] = useState("");
const [scale, setScale] = useState("c");
// Compute temperatures in both scales
const tempCelsius =
scale === "f" ? tryConvert(temperature, toCelsius) : temperature;
const tempFahrenheit =
scale === "c" ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<View style={styles.container}>
{/* Display and input in Celsius degrees */}
<TemperatureInput
value={tempCelsius}
scale="c"
onChange={(newTemp) => {
setTemperature(newTemp);
setScale("c");
}}
/>
{/* Display and input in Fahrenheit degrees */}
<TemperatureInput
value={tempFahrenheit}
scale="f"
onChange={(newTemp) => {
setTemperature(newTemp);
setScale("f");
}}
/>
<BoilingResult tempCelsius={parseFloat(tempCelsius)} />
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
text: {
fontSize: 22,
paddingBottom: 10,
},
});
components/
rassemble les définitions des composants élémentaires.App.js
.utils/
et theme/
factorisent les fonctions de conversion et les styles React Native communs.MyApp/
├── components/
│ ├── BoilingResult.js
│ ├── TemperatureInput.js
├── utils/
│ ├── temperatureUtils.js
├── theme/
│ ├── styles.js
└── App.js
Dans le fichier utils/temperatureUtils.js
.
// Scale names used for display
export const scaleNames = { c: "Celsius", f: "Fahrenheit" };
// Celsius/Fahrenheit conversion functions
export function toCelsius(fahrenheit) {
return ((fahrenheit - 32) * 5) / 9;
}
export function toFahrenheit(celsius) {
return (celsius * 9) / 5 + 32;
}
// Convert a temperature using a given conversion function
export function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return "";
}
// Call the conversion function on input
const output = convert(input);
// Keep the output rounded to the third decimal place:
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
Dans le fichier components/TemperatureInput.js
.
import React from "react";
import { TextInput } from "react-native";
import { scaleNames } from "../utils/temperatureUtils";
import styles from "../theme/styles";
// Component for displaying and inputting a temperature in a specific scale
const TemperatureInput = ({ value, scale, onChange }) => {
// ...
};
export default TemperatureInput;
Dans le fichier App.js
.
import React, { useState } from "react";
import { View } from "react-native";
import BoilingResult from "./components/BoilingResult";
import TemperatureInput from "./components/TemperatureInput";
import { toCelsius, toFahrenheit, tryConvert } from "./utils/temperatureUtils";
import styles from "./theme/styles";
// Main component
export default App = () => {
// ...
};
Utiliser npx expo install
au lieu de npm install
assure l’installation de versions compatibles avec celle d’Expo.
# Core components and common dependencies
npm install @react-navigation/native
npx expo install react-native-screens react-native-safe-area-context
# If StackNavigator is used
npm install @react-navigation/native-stack
# If BottomTabNavigator is used
npm install @react-navigation/bottom-tabs
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
export default App = () => {
return (
<NavigationContainer>{/* Rest of your app code */}</NavigationContainer>
);
};
Principe similaire au web : gestion d’une pile d’écrans avec possibilité de naviguer de l’un à l’autre.
import React from "react";
import { StyleSheet, View, Text } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
const HomeScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the home screen</Text>
</View>
);
};
const Stack = createNativeStackNavigator();
export default App = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
text: { fontSize: 18, paddingBottom: 10 },
});
Passé comme prop d’un composant écran, l’objet navigation
permet la gestion de la navigation:
navigation.navigate("RouteName")
navigue vers un nouvel écran, sauf s’il est déjà l’écran actuel.navigation.push("RouteName")
navigue vers un nouvel écran même s’il est déjà l’écran actuel.navigation.goBack()
permet de revenir en arrière dans la navigation.import React from "react";
import { StyleSheet, View, Text, Button } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the home screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate("Details")}
/>
</View>
);
};
const DetailsScreen = ({ navigation }) => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the details screen</Text>
<Button
title="Go to Details... again"
onPress={() => navigation.push("Details")}
/>
<Button title="Go back" onPress={() => navigation.goBack()} />
<Button title="Go to Home" onPress={() => navigation.navigate("Home")} />
</View>
);
};
const Stack = createNativeStackNavigator();
export default App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
text: { fontSize: 18, paddingBottom: 10 },
});
navigation.navigate
permet de transmettre des données d’un écran à l’autre sous la forme d’un objet JavaScript.
// Navigate to "RouteName", passing some parameters
navigation.navigate("RouteName", { /* params go here */ })
L’écran d’arrivée peut lire les paramètres grâce à la propriété params
du prop route
.
Ces paramètres sont équivalents à ceux d’une URL. Ils ne doivent pas contenir les données métiers de l’application (plutôt gérés via l’état).
import React from "react";
import { StyleSheet, View, Text, Button } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the home screen</Text>
<Button
title="Go to Details"
onPress={() => {
// Navigate to the Details route with 2 params
navigation.navigate("Details", {
itemId: 86,
otherParam: "anything you want here",
});
}}
/>
</View>
);
};
const DetailsScreen = ({ navigation, route }) => {
// Get the params
const { itemId, otherParam } = route.params;
return (
<View style={styles.container}>
<Text style={styles.text}>This is the details screen</Text>
{/* Convert params to JSON strings before display */}
<Text>itemId: {JSON.stringify(itemId)}</Text>
<Text>otherParam: {JSON.stringify(otherParam)}</Text>
<Button
title="Go to Details... again"
onPress={() => {
// Navigate to Details again with one param
navigation.push("Details", {
itemId: Math.floor(Math.random() * 100),
});
}}
/>
<Button title="Go back" onPress={() => navigation.goBack()} />
<Button title="Go to Home" onPress={() => navigation.navigate("Home")} />
</View>
);
};
const Stack = createNativeStackNavigator();
export default App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
text: { fontSize: 18, paddingBottom: 10 },
});
La prop screenOptions
de StackNavigator
permet de configurer l’en-tête commun à tous les écrans. La couleur de la barre de statut du téléphone peut être harmonisée grâce au composant StatusBar.
import React from "react";
import { StyleSheet, View, Text, Button, StatusBar } from "react-native";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
// [...]
const Stack = createNativeStackNavigator();
export default App = () => {
return (
<NavigationContainer>
<StatusBar barStyle="light-content" backgroundColor="#f4511e" />
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: {
backgroundColor: "#f4511e",
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold",
},
}}
>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: "My home" }}
/>
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
container: { flex: 1, alignItems: "center", justifyContent: "center" },
text: { fontSize: 18, paddingBottom: 10 },
});
screens/
stocke les composants définissant chaque écran de l’application.navigation/
stocke les composants qui organisent la navigation entre les écrans.MyApp/
├── screens/
│ ├── DetailsScreen.js
│ ├── HomeScreen.js
├── navigation/
│ ├── RootStackNavigator.js
├── theme/
│ ├── colors.js
│ ├── styles.js
└── App.js
Affichage d’onglets en bas de l’écran.
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import Ionicons from "react-native-vector-icons/Ionicons";
import { NavigationContainer } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
const HomeScreen = () => {
return (
<View style={styles.container}>
<Text>This is the home screen</Text>
</View>
);
};
const SettingsScreen = () => {
return (
<View style={styles.container}>
<Text>This is the settings screen</Text>
</View>
);
};
const Tab = createBottomTabNavigator();
export default App = () => {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
});
Réalisé grâce à la prop screenOptions
de TabNavigator
.
import React from "react";
import { StyleSheet, Text, View, Button, StatusBar } from "react-native";
import Ionicons from "react-native-vector-icons/Ionicons";
import { NavigationContainer } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
// [...]
const Tab = createBottomTabNavigator();
export default App = () => {
return (
<NavigationContainer>
<StatusBar barStyle="light-content" backgroundColor="#f4511e" />
<Tab.Navigator
screenOptions={({ route }) => ({
// Icons will be different if the tab is focused
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === "Home") {
iconName = focused
? "ios-information-circle"
: "ios-information-circle-outline";
} else if (route.name === "Settings") {
iconName = focused ? "ios-list" : "ios-list-outline";
}
// You can return any component that you like here!
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: "tomato",
tabBarInactiveTintColor: "gray",
headerStyle: {
backgroundColor: "#f4511e",
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold",
},
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
text: { fontSize: 18, paddingBottom: 10 },
});
StackNavigator
.navigation
doit être passé comme prop de l’écran.import React from "react";
import { StyleSheet, Text, View, Button } from "react-native";
import Ionicons from "react-native-vector-icons/Ionicons";
import { NavigationContainer } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the home screen</Text>
<Button
title="Go to Settings"
onPress={() => navigation.navigate("Settings")}
/>
</View>
);
};
const SettingsScreen = ({ navigation }) => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the settings screen</Text>
<Button title="Go to Home" onPress={() => navigation.navigate("Home")} />
</View>
);
};
const Tab = createBottomTabNavigator();
export default App = () => {
return (
<NavigationContainer>
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === "Home") {
iconName = focused
? "ios-information-circle"
: "ios-information-circle-outline";
} else if (route.name === "Settings") {
iconName = focused ? "ios-list" : "ios-list-outline";
}
// You can return any component that you like here!
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: "tomato",
tabBarInactiveTintColor: "gray",
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
text: { fontSize: 18, paddingBottom: 10 },
});
import React from "react";
import { StyleSheet, Text, View, Button, StatusBar } from "react-native";
import Ionicons from "react-native-vector-icons/Ionicons";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
const HomeScreen = ({ navigation }) => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the home screen</Text>
<Button
title="Go to Details"
onPress={() => navigation.navigate("Details")}
/>
</View>
);
};
const DetailsScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the details screen</Text>
</View>
);
};
const SettingsScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>This is the settings screen</Text>
</View>
);
};
// Screen stack for home tab
const HomeStack = createNativeStackNavigator();
const HomeStackNavigator = () => {
return (
<HomeStack.Navigator initialRouteName="Home" screenOptions={screenOptions}>
<HomeStack.Screen
name="Home"
component={HomeScreen}
options={{ title: "My home" }}
/>
<HomeStack.Screen name="Details" component={DetailsScreen} />
</HomeStack.Navigator>
);
};
// Screen stack for settings tab
const SettingsStack = createNativeStackNavigator();
const SettingsStackNavigator = () => {
return (
<SettingsStack.Navigator
initialRouteName="Home"
screenOptions={screenOptions}
>
<SettingsStack.Screen name="Settings" component={SettingsScreen} />
</SettingsStack.Navigator>
);
};
const Tab = createBottomTabNavigator();
export default App = () => {
return (
<NavigationContainer>
<StatusBar barStyle="light-content" backgroundColor="#f4511e" />
<Tab.Navigator
screenOptions={({ route }) => ({
// Icons will be different if the tab is focused
tabBarIcon: ({ focused, color, size }) => {
let iconName;
if (route.name === "HomeStack") {
iconName = focused
? "ios-information-circle"
: "ios-information-circle-outline";
} else if (route.name === "SettingsStack") {
iconName = focused ? "ios-list" : "ios-list-outline";
}
// You can return any component that you like here!
return <Ionicons name={iconName} size={size} color={color} />;
},
tabBarActiveTintColor: "tomato",
tabBarInactiveTintColor: "gray",
// Hiding tab navigator header to show only stack header
headerShown: false,
})}
>
<Tab.Screen name="HomeStack" component={HomeStackNavigator} />
<Tab.Screen name="SettingsStack" component={SettingsStackNavigator} />
</Tab.Navigator>
</NavigationContainer>
);
};
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: "center", alignItems: "center" },
text: { fontSize: 18, paddingBottom: 10 },
});
// Common stack header options
const screenOptions = {
headerStyle: {
backgroundColor: "#f4511e",
},
headerTintColor: "#fff",
headerTitleStyle: {
fontWeight: "bold",
},
};
MyApp/
├── screens/
│ ├── DetailsScreen.js
│ ├── HomeScreen.js
│ ├── SettingsScreen.js
├── navigation/
│ ├── HomeStackNavigator.js
│ ├── SettingsStackNavigator.js
│ ├── RootTabNavigator.js
├── theme/
│ ├── colors.js
│ ├── styles.js
└── App.js
Une API (Application Programming Interface) est un point d’entrée programmatique dans un système.
Elle fournit un moyen d’interagir avec ce système.
Les API permettent aux développeurs d’intégrer des services externes dans leurs applications.
Une API web (appelée parfois service web) est une API accessible via les technologies du web : HTTP ou HTTPS.
Les API web utilisent le plus souvent le format de donnée JSON.
Certaines sont librement utilisables, d’autres nécessitent une authentification du client.
fetch
Une promesse (promise) est un objet qui encapsule une opération dont le résultat n’est pas encore connu.
La fonction JavaScript fetch() exploite les possibilités des promesses.
// Envoie une requête HTTP asynchrone vers l'URL spécifiée
fetch(url)
.then(() => {
// Code appelé ultérieurement si la requête réussit
})
.catch(() => {
// Code appelé ultérieurement si la requête échoue
});
// Envoi d'une requête HTTP asynchrone vers l'URL spécifiée
// La réponse reçue ici contient des données JSON
fetch("http://my-api-url")
// Accès au contenu JSON de la réponse
.then((response) => response.json())
.then((content) => {
// Utilisation du contenu de la réponse
// `content` est un objet ou un tableau JavaScript
// ...
})
.catch((error) => {
console.error(error);
});
fetch("https://mywebsite.com/endpoint/", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
firstParam: "yourValue",
secondParam: "yourOtherValue",
}),
});
async/await
Les fonctions utilisant await
doivent être déclarées avec le mot-clé async
.
// Cette fonction renvoie une promesse
const fetchRemoteApi = async () => {
try {
// Envoi d'une requête HTTP asynchrone vers l'URL spécifiée
// La réponse reçue ici contient des données JSON
const response = await fetch("http://my-api-url");
// Accès au contenu JSON de la réponse
const content = await response.json();
// Utilisation du contenu de la réponse
// `content` est un objet ou un tableau JavaScript
// ...
} catch (e) {
// Gestion de l'erreur
// ...
}
};
fetchRemoteApi();
hook
.useEffect(() => {
// Code exécuté uniquement au chargement (mounting) du composant
// ...
}, []);
loading
ajouté à l’état du composant.import React, { useEffect } from "react";
import { useState } from "react";
import {
StyleSheet,
Text,
View,
ActivityIndicator,
TouchableOpacity,
} from "react-native";
// API endpoint
const rootEndpoint = "https://api.punkapi.com/v2";
// fetch API for a random beer
const fetchRandomBeer = async () => {
const response = await fetch(`${rootEndpoint}/beers/random`);
const beers = await response.json();
// Access first element of returned beer array
return beers[0];
};
export default App = () => {
// Define state
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [beerName, setBeerName] = useState("");
const [beerDescription, setBeerDescription] = useState("");
// Load a new beer
const loadBeer = async () => {
setLoading(true);
setError(false);
try {
const beer = await fetchRandomBeer();
// Update state
setBeerName(beer.name);
setBeerDescription(beer.description);
} catch (e) {
setError(true);
}
setLoading(false);
};
// The empty array [] prevents the effect from running at each re-render
useEffect(() => {
// More details; https://www.robinwieruch.de/react-hooks-fetch-data/
loadBeer();
}, []);
if (loading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" />
</View>
);
}
if (error) {
return (
<View style={styles.container}>
<Text>Something went wrong :\</Text>
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.name}>{beerName}</Text>
<Text style={styles.description}>{beerDescription}</Text>
{/* Add a button to fetch another beer */}
<TouchableOpacity
style={styles.button}
onPress={() => {
loadBeer();
}}
>
<Text>Grab a new beer!</Text>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
margin: 30,
},
name: {
fontSize: 18,
fontWeight: "700",
marginBottom: 10,
},
description: {
marginBottom: 10,
},
button: {
borderWidth: 1,
borderColor: "black",
borderRadius: 3,
padding: 5,
justifyContent: "center",
alignItems: "center",
},
});
api/
rassemble les définitions des appels réseau asynchrones.
MyApp/
├── api/
│ ├── punkapi.js
├── theme/
│ ├── styles.js
└── App.js
AsyncStorage
npx expo install @react-native-async-storage/async-storage
setItem
permet à la fois d’ajouter de nouveaux éléments et de modifier des éléments existants.
import AsyncStorage from "@react-native-async-storage/async-storage";
const storeString = async (value) => {
try {
await AsyncStorage.setItem("my_storage_key", value);
} catch (e) {
// Saving error
}
};
const storeObject = async (value) => {
try {
const jsonValue = JSON.stringify(value)
await AsyncStorage.setItem("my_storage_key", jsonValue)
} catch (e) {
// Saving error
}
}
getItem
renvoie une promesse qui réussit si la valeur associée à la clé est trouvée, ou renvoie null
dans le cas contraire.
import AsyncStorage from "@react-native-async-storage/async-storage";
const getString = async () => {
try {
const value = await AsyncStorage.getItem("my_storage_key");
if (value !== null) {
// value previously stored
}
} catch (e) {
// Error reading value
}
};
const getObject = async () => {
try {
const jsonValue = await AsyncStorage.getItem("my_storage_key");
return jsonValue != null ? JSON.parse(jsonValue) : null;
} catch (e) {
// Error reading value
}
};
import React, { useEffect, useState } from "react";
import { StyleSheet, Text, View, TextInput, FlatList } from "react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";
// AsyncStorage key used for storing ideas
const STORAGE_KEY = "ASYNC_STORAGE_IDEAS";
export default function App() {
// Value of the text input
const [input, setInput] = useState("");
// Ideas list, initially empty
const [ideas, setIdeas] = useState([]);
// Clear local storage
const resetIdeas = async () => {
console.log("Removing ideas from local storage...");
try {
await AsyncStorage.multiRemove([STORAGE_KEY]);
} catch (e) {
console.error("Failed to clear ideas");
}
};
// Save ideas array parameter to local storage
const saveIdeas = async (newIdeas) => {
console.log(`Saving ideas [${newIdeas}] to local storage...`);
try {
// Turn ideas array into a JSON string
const jsonIdeas = JSON.stringify(newIdeas);
// Store ideas string
await AsyncStorage.setItem(STORAGE_KEY, jsonIdeas);
} catch (e) {
console.error("Failed to save ideas");
}
};
// Load ideas from local storage
const loadIdeas = async () => {
console.log("Loading ideas from local storage...");
try {
// Load ideas string
const jsonIdeas = await AsyncStorage.getItem(STORAGE_KEY);
if (jsonIdeas !== null) {
// Turn stored JSON string into an array, and set it as ideas array
setIdeas(JSON.parse(jsonIdeas));
}
} catch (e) {
console.error("Failed to load ideas");
}
};
// Load ideas only during initial component mounting
useEffect(() => {
// Uncomment to clear ideas from local storage
// resetIdeas();
loadIdeas();
}, []);
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Enter your newest brilliant idea"
// Display input value
value={input}
onChangeText={(text) => {
// Update input value
setInput(text);
}}
onSubmitEditing={() => {
if (!input) return; // Don't submit if empty
// Creating a new ideas array with input (new idea) at the end
const newIdeas = [...ideas, input];
saveIdeas(newIdeas);
// Update state
setIdeas(newIdeas);
// Reset input value
setInput("");
}}
/>
<FlatList
style={styles.list}
data={ideas}
renderItem={({ item }) => <Text style={styles.item}>- {item}</Text>}
keyExtractor={(item) => item}
></FlatList>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: 30,
},
input: {
backgroundColor: "whitesmoke",
padding: 10,
marginBottom: 10,
},
list: {
marginLeft: 10,
marginRight: 10,
},
item: {
padding: 5,
},
});
MyApp/
├── components/
│ ├── Input.js
├── utils/
│ ├── localStorage.js
├── theme/
│ ├── styles.js
└── App.js