Créer une application React Native réelle à partir de zéro avec Expo et Metricalp (2024)
Nous savons que vous n'aimez pas trop lire les documentations et les modèles prédéfinis. C'est pourquoi nous avons préparé un exemple réel pour vous. Dans cet article, nous allons créer une application React Native à partir de zéro avec Expo et Metricalp.
Introduction
Nous allons développer une application ToDo en React Native. L'utilisateur peut ajouter, mettre à jour et supprimer des tâches. Mais, en imaginant cela comme une application du monde réel, nous aurons des publicités agaçantes à l'écran 😬. L'utilisateur peut passer à un compte PRO pour se débarrasser des publicités. Ainsi, notre application aura un deuxième écran /upgrade-pro. Sur cet écran, nous aurons un bouton pour passer à PRO. Nous utiliserons Metricalp pour suivre les comportements des utilisateurs et analyser notre application. Fais-moi confiance, ça va être amusant.
Pour en faire une application du monde réel, nous utiliserons quelques bibliothèques géniales dans notre application, comme le Zustand store (pour gérer l'état) et le MMKV store (pour stocker les données en local de manière ultra-rapide). Voici quelques captures finales de notre application :
Ok, assez parlé, construisons-la 🚀!
Configuration du Projet
Tout d'abord, nous devons créer un nouveau projet React Native avec Expo :
npx create-expo-app@latest
Dans cet exemple, nous utiliserons principalement le simulateur iOS. Donc, vous devez avoir Xcode installé sur votre machine. Si vous n'avez pas Xcode, vous pouvez l'installer depuis l'App Store. Nous allons également installer watchman pour travailler correctement avec le simulateur :
brew update
brew install watchman
Ensuite, nous allons installer le client de développement Expo pour travailler avec notre application dans le simulateur :
npx expo install expo-dev-client
Assurez-vous que vous avez la dernière version du simulateur installée. Vous pouvez vérifier dans XCode -> Preferences -> Components.
Lançons notre application pour la première fois, à l'intérieur du dossier de notre nouvelle application, exécutez :
npx expo run:ios
Installer les Bibliothèques
Maintenant, nous allons installer quelques bibliothèques pour nous faciliter la vie. D'abord, nous allons installer la bibliothèque react-native-mmkv store de mrousavy. Elle fonctionne sur la nouvelle architecture de React Native, donc vous pouvez lire/écrire sur le stockage local de manière synchronisée et rapide :
npx expo install react-native-mmkv
À ce stade, nous devons également exécuter expo prebuild
npx expo prebuild
Pour suivre nos utilisateurs et leurs tâches, nous allons utiliser des UUIDs générés aléatoirement. Nous allons installer la bibliothèque crypto d'expo pour générer ces UUIDs :
npx expo install expo-crypto
Nous garderons les tâches dans le Zustand store (une bibliothèque de gestion de l'état), nous devons donc l'installer :
yarn add zustand
Enfin, nous allons installer Metricalp pour suivre les comportements des utilisateurs dans notre application (au moment où nous écrivons cet article, la dernière version est la 1.0.12 mais installez toujours la dernière) :
yarn add @metricalp/react-native
Structure de l'App
Voici la structure finale de l'application. Nous expliquerons tous les fichiers utilisés ci-dessous :
Commençons par quelques bases. Créez d'abord un dossier nommé 'utils' à la racine du projet.
Ensuite, créez un fichier nommé zustand-store.ts dans le dossier utils. Ce fichier sera utilisé pour gérer notre état dans l'application :
import { unstable_batchedUpdates } from 'react-native'; // or 'react-native'
import { create } from 'zustand';
import * as Crypto from 'expo-crypto';
import { storage } from './mmkv-store';
interface Todo {
id: string;
text: string;
}
interface ZustandStore {
todos: Todo[];
}
export const useZustandStore = create<ZustandStore>((set) => ({
todos: [],
}));
export const setTodos = (todos: Todo[]) => {
unstable_batchedUpdates(() => {
useZustandStore.setState({ todos });
});
storage.set('USER_TODOS_KEY', JSON.stringify(todos));
};
export const updateTodo = (id: string, text: string) => {
const todos = useZustandStore.getState().todos;
const updatedTodos = todos.map((todo) => {
if (todo.id === id) {
return { ...todo, text };
}
return todo;
});
setTodos(updatedTodos);
};
export const addTodo = (text: string) => {
const todos = useZustandStore.getState().todos;
const newTodo = { id: Crypto.randomUUID(), text };
setTodos([...todos, newTodo]);
};
export const deleteTodo = (id: string) => {
const todos = useZustandStore.getState().todos;
const updatedTodos = todos.filter((todo) => todo.id !== id);
setTodos(updatedTodos);
};
export const resetTodos = () => {
setTodos([]);
};
Fondamentalement, nous avons défini un Zustand store avec les tâches. Nous avons des fonctions pour ajouter, mettre à jour, supprimer et réinitialiser les tâches. Nous utilisons mmkv-store pour stocker les tâches dans le stockage local. Nous mettons également à jour cette copie de stockage local à chaque mise à jour de l'état. Nous générons des UUIDs aléatoires pour chaque nouvelle tâche.
Maintenant, générons un fichier nommé mmkv-store.ts dans le dossier utils :
import { MMKV } from 'react-native-mmkv';
export const storage = new MMKV();
Maintenant, générons un fichier nommé constants.ts dans le dossier utils. Nous garderons les constantes ici :
export const METR_UUID_KEY = 'metr.uuid';
export const USER_TODOS_KEY = 'user.todos';
export const METRICALP_TID = 'mam48'; // Remplacez avec votre TID de Metricalp
export const SCREENS = {
INDEX: 'index',
UPGRADE_PRO: 'upgrade-pro',
};
Enfin, générons un fichier nommé metricalp-events.ts dans le dossier utils. Nous garderons nos événements personnalisés pour Metricalp dans ce fichier :
export const MetricalpCustomEvents = {
UPGRADE_PRO_NAVIGATOR: 'upgrade_pro_navigator',
TODO_DELETE_CLICK: 'todo_delte_click',
TODO_EDIT_CLICK: 'todo_edit_click',
ADD_TODO_CLICK: 'add_todo_click',
UPDATE_TODO_CLICK: 'update_todo_click',
UPGRADE_PRO_CLICK: 'upgrade_pro_click',
};
Nous allons suivre les clics d'édition et de suppression des tâches, le clic pour ajouter des tâches, le clic pour mettre à jour les tâches et les clics pour passer à PRO dans notre application. De plus, nous voulons suivre comment l'utilisateur navigue vers l'écran de mise à niveau, c'est pourquoi nous avons un autre événement upgrade_pro_navigator que nous déclencherons chaque fois qu'un utilisateur navigue vers l'écran de mise à niveau. Nous attacherons une propriété à cet événement pour enregistrer comment l'utilisateur a navigué vers la mise à niveau. Nous enregistrerons également combien de tâches l'utilisateur avait lorsqu'il a décidé de passer à la mise à niveau. Parce que cela pourrait être une métrique importante (l'équipe marketing a dit cela). De plus, nous voulons enregistrer le moment de la journée (matin, après-midi, soir ou nuit) pour tous ces événements. Nous voulons comprendre le comportement des utilisateurs, peut-être qu'ils ont tendance à utiliser l'application le soir, mais ils sont plus enclins à passer à la mise à niveau le matin ? Encore une fois, l'équipe marketing a demandé ces données. Dans Metricalp, nous pouvons tout garder, nous pouvons avoir des scénarios illimités 🤝
Maintenant, vérifions le fichier app/_layout.tsx, qui est notre fichier de mise en page principal, nous initialiserons Metricalp et notre application ici :
import {
DarkTheme,
DefaultTheme,
ThemeProvider,
} from '@react-navigation/native';
import { useFonts } from 'expo-font';
import { Stack, usePathname } from 'expo-router';
import * as SplashScreen from 'expo-splash-screen';
import { useEffect, useRef, useState } from 'react';
import 'react-native-reanimated';
import * as Crypto from 'expo-crypto';
import { useColorScheme } from '@/hooks/useColorScheme';
import { storage } from '@/utils/mmkv-store';
import {
METRICALP_TID,
METR_UUID_KEY,
USER_TODOS_KEY,
} from '@/utils/constants';
import { Metricalp } from '@metricalp/react-native';
import { setTodos } from '@/utils/zustand-store';
import { AppState, NativeEventSubscription, Platform } from 'react-native';
// Empêche l'écran de démarrage de se masquer automatiquement avant que le chargement des actifs soit terminé.
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
});
const [initializationCompleted, setInitializationCompleted] = useState(false);
const pathname = usePathname();
const lastPathnameRef = useRef<string>('/');
lastPathnameRef.current = pathname;
useEffect(() => {
let subscription: NativeEventSubscription;
let userUUID = storage.getString(METR_UUID_KEY);
if (!userUUID) {
userUUID = Crypto.randomUUID();
storage.set(METR_UUID_KEY, userUUID);
}
let todos = [];
const userTODOsFromMMKVStore = storage.getString(USER_TODOS_KEY);
if (userTODOsFromMMKVStore) {
todos = JSON.parse(userTODOsFromMMKVStore);
}
setTodos(todos);
const osWithVersion =
(Platform.OS === 'ios' ? 'iOS' : 'Android') + ' ' + Platform.Version;
Metricalp.init({
platform: Platform.OS,
app: '[email protected]',
language: 'French-FR',
os: osWithVersion,
uuid: userUUID,
tid: METRICALP_TID,
});
setInitializationCompleted(true);
subscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'background' || nextAppState === 'inactive') {
Metricalp.appLeaveEvent();
} else if (nextAppState === 'active') {
Metricalp.screenViewEvent(lastPathnameRef.current);
}
});
return () => {
subscription?.remove();
};
}, []);
useEffect(() => {
if (!initializationCompleted) {
return;
}
Metricalp.screenViewEvent(pathname);
}, [pathname, initializationCompleted]);
useEffect(() => {
if (loaded && initializationCompleted) {
SplashScreen.hideAsync();
}
}, [loaded, initializationCompleted]);
if (!loaded || !initializationCompleted) {
return null;
}
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
</ThemeProvider>
);
}
Nous initialisons d'abord nos tâches depuis le stockage local (mmkv store). Ensuite, nous initialisons Metricalp avec quelques informations de base telles que la plateforme, le nom de l'application, la langue, le système d'exploitation, l'UUID et le TID. Nous suivons également les changements d'état de l'application pour comprendre quand l'application passe en arrière-plan ou en premier plan. Nous suivons également les vues d'écran dans notre application, nous utilisons le hook usePathname pour obtenir le chemin actuel de notre application. Nous cachons l'écran de démarrage lorsque toutes ces initialisations sont terminées.
Nous générons un UUID par utilisateur et le conservons dans le stockage local. Metricalp utilise cet UUID pour identifier l'utilisateur et compter les vues/événements uniques. Vous pouvez également utiliser user_id, etc., si vous le souhaitez.
Lorsque l'application passe en arrière-plan ou devient inactive, nous générons des événements appLeaveEvent à partir de la bibliothèque Metricalp. Cela nous aide à enregistrer les durées des vues d'écran, etc.
Nous avons généré un hook personnalisé useDayTime dans le dossier hooks (hooks/useDayTime.ts) pour obtenir l'heure de la journée de l'utilisateur :
export function useDayTime() {
const now = new Date();
const hours = now.getHours();
if (hours >= 6 && hours < 12) {
return 'matin';
}
if (hours >= 12 && hours < 18) {
return 'après-midi';
}
if (hours >= 18 && hours < 24) {
return 'soir';
}
return 'nuit';
}
Maintenant, modifions le fichier (tabs)/_layout.tsx pour avoir un menu de mise à niveau de l'onglet et déclencher l'événement upgrade_pro_navigator lorsque vous cliquez sur ce menu :
import { Tabs } from 'expo-router';
import React from 'react';
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
import { Colors } from '@/constants/Colors';
import { useColorScheme } from '@/hooks/useColorScheme';
import { SCREENS } from '@/utils/constants';
import { Metricalp } from '@metricalp/react-native';
import { MetricalpCustomEvents } from '@/utils/metricalp-events';
import { useZustandStore } from '@/utils/zustand-store';
import { useDayTime } from '@/hooks/useDayTime';
export default function TabLayout() {
const colorScheme = useColorScheme();
const dayTime = useDayTime();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
headerShown: false,
}}
screenListeners={{
tabPress: (e) => {
if (e.target?.startsWith(SCREENS.UPGRADE_PRO)) {
Metricalp.customEvent(MetricalpCustomEvents.UPGRADE_PRO_NAVIGATOR, {
day_time: dayTime,
placement: 'bottom-tab',
current_todos_count: useZustandStore.getState().todos.length,
});
}
},
}}
>
<Tabs.Screen
name={SCREENS.INDEX}
options={{
title: 'Accueil',
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={focused ? 'home' : 'home-outline'}
color={color}
/>
),
}}
/>
<Tabs.Screen
name={SCREENS.UPGRADE_PRO}
options={{
title: 'Upgrade Pro',
tabBarIcon: ({ color, focused }) => (
<TabBarIcon
name={focused ? 'arrow-up-circle' : 'arrow-up-circle-outline'}
color={color}
/>
),
}}
/>
</Tabs>
);
}
Voyez que nous attachons également les propriétés placement, current_todos_count et day_time à notre événement personnalisé. Facile et génial, n'est-ce pas?
Modifions maintenant le fichier (tabs)/index.tsx pour avoir notre écran principal de l'application :
import { SCREENS } from '@/utils/constants';
import {
addTodo,
deleteTodo,
updateTodo,
useZustandStore,
} from '@/utils/zustand-store';
import { useRouter } from 'expo-router';
import React, { useState } from 'react';
import {
View,
Text,
TextInput,
TouchableOpacity,
FlatList,
StyleSheet,
} from 'react-native';
import { Metricalp } from '@metricalp/react-native';
import { MetricalpCustomEvents } from '@/utils/metricalp-events';
import { useDayTime } from '@/hooks/useDayTime';
export default function HomeScreen() {
const todos = useZustandStore((state) => state.todos);
const [todoInput, setTodoInput] = useState('');
const [editId, setEditId] = useState('');
const dayTime = useDayTime();
const router = useRouter();
const handleAddTask = () => {
if (editId) {
Metricalp.customEvent(MetricalpCustomEvents.UPDATE_TODO_CLICK, {
day_time: dayTime,
});
updateTodo(editId, todoInput);
} else {
Metricalp.customEvent(MetricalpCustomEvents.ADD_TODO_CLICK, {
day_time: dayTime,
});
addTodo(todoInput);
}
setEditId('');
setTodoInput('');
};
const handleEditTask = (id: string) => {
const taskToEdit = todos.find((todo) => todo.id === id);
Metricalp.customEvent(MetricalpCustomEvents.TODO_EDIT_CLICK, {
day_time: dayTime,
});
setTodoInput(taskToEdit?.text || '');
setEditId(id);
};
const handleDeleteTask = (id: string) => {
Metricalp.customEvent(MetricalpCustomEvents.TODO_DELETE_CLICK, {
day_time: dayTime,
});
deleteTodo(id);
};
const renderItem = ({ item }: any) => (
<View style={styles.task}>
<Text style={styles.itemList}>{item.text}</Text>
<View style={styles.taskButtons}>
<TouchableOpacity onPress={() => handleEditTask(item.id)}>
<Text style={styles.editButton}>Modifier</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleDeleteTask(item.id)}>
<Text style={styles.deleteButton}>Supprimer</Text>
</TouchableOpacity>
</View>
</View>
);
const renderAds = (adsPlace: 'top' | 'bottom') => {
return (
<>
<View style={styles.adsContainer}>
<Text style={styles.adsText}>Voici quelques annonces</Text>
</View>
<TouchableOpacity
style={styles.upgradeButton}
onPress={() => {
Metricalp.customEvent(MetricalpCustomEvents.UPGRADE_PRO_NAVIGATOR, {
placement: adsPlace,
day_time: dayTime,
current_todos_count: todos.length,
});
router.push(SCREENS.UPGRADE_PRO);
}}
>
<Text style={styles.upgradeButtonText}>
Passez à Pro maintenant pour une expérience sans publicité
</Text>
</TouchableOpacity>
</>
);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Application de Liste de Tâches</Text>
{renderAds('top')}
<TextInput
style={styles.input}
placeholder="Entrez une tâche"
value={todoInput}
onChangeText={(text) => setTodoInput(text)}
/>
<TouchableOpacity style={styles.addButton} onPress={handleAddTask}>
<Text style={styles.addButtonText}>
{editId ? 'Mettre à jour la tâche' : 'Ajouter une tâche'}
</Text>
</TouchableOpacity>
<FlatList
data={todos}
renderItem={renderItem}
keyExtractor={(item) => item.id}
/>
{renderAds('bottom')}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 40,
marginTop: 40,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
heading: {
fontSize: 30,
fontWeight: 'bold',
marginBottom: 7,
color: 'green',
},
adsContainer: {
borderWidth: 2,
borderColor: 'purple',
marginTop: 10,
padding: 5,
borderRadius: 10,
},
adsText: {
fontSize: 14,
fontWeight: 'bold',
color: 'purple',
},
input: {
borderWidth: 3,
borderColor: '#ccc',
padding: 10,
marginBottom: 10,
marginTop: 20,
borderRadius: 10,
fontSize: 18,
},
addButton: {
backgroundColor: 'green',
padding: 10,
borderRadius: 5,
marginBottom: 10,
},
addButtonText: {
color: 'white',
fontWeight: 'bold',
textAlign: 'center',
fontSize: 18,
},
task: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 15,
fontSize: 18,
},
itemList: {
fontSize: 19,
},
taskButtons: {
flexDirection: 'row',
},
editButton: {
marginRight: 10,
color: 'green',
fontWeight: 'bold',
fontSize: 18,
},
deleteButton: {
color: 'red',
fontWeight: 'bold',
fontSize: 18,
},
upgradeButton: {
backgroundColor: 'purple',
padding: 10,
marginVertical: 10,
borderRadius: 5,
marginBottom: 10,
},
upgradeButtonText: {
color: 'white',
},
});
Nous avons donc une application ToDo standard. Nous pouvons ajouter/modifier/supprimer des tâches. Nous avons des annonces agaçantes et des boutons de mise à niveau à côté des annonces.
Nous suivons chaque clic pour ajouter/modifier/supprimer via Metricalp et nous ajoutons la propriété day_time à tous ces événements.
Mais nous ajoutons également les propriétés placement et current_todos_count à l'événement upgrade_pro_navigator. Parce qu'elles sont importantes pour nous d'un point de vue marketing.
Enfin, créez le fichier (tabs)/upgrade-pro.tsx pour avoir notre écran de mise à niveau Pro :
import { useDayTime } from '@/hooks/useDayTime';
import { MetricalpCustomEvents } from '@/utils/metricalp-events';
import { useZustandStore } from '@/utils/zustand-store';
import { Metricalp } from '@metricalp/react-native';
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
export default function UpgradeProScreen() {
const dayTime = useDayTime();
return (
<View style={styles.container}>
<Text style={styles.title}>Application de Liste de Tâches</Text>
<Text>
Vous ne verrez plus jamais de publicités dans l'application lorsque vous passez à PRO.
</Text>
<TouchableOpacity
style={styles.upgradeButton}
onPress={() => {
Metricalp.customEvent(MetricalpCustomEvents.UPGRADE_PRO_CLICK, {
day_time: dayTime,
current_todos_count: useZustandStore.getState().todos.length,
});
alert('Mise à niveau vers Pro maintenant');
}}
>
<Text style={styles.upgradeButtonText}>Passez à Pro maintenant</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 40,
marginTop: 40,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
},
upgradeButton: {
backgroundColor: 'purple',
padding: 10,
marginVertical: 20,
borderRadius: 5,
marginBottom: 10,
},
upgradeButtonText: {
color: 'white',
},
});
Ici, nous générons un événement personnalisé upgrade_pro_click lorsque l'utilisateur clique sur le bouton de mise à niveau. Nous ajoutons également les propriétés day_time et current_todos_count à cet événement. Nous montrons également une alerte à l'utilisateur indiquant qu'il a mis à niveau.
current_todos_count peut être important car nous pouvons mesurer quand un utilisateur décide de passer à la mise à niveau en fonction du nombre de tâches qu'il a. Peut-être pouvons-nous afficher une réduction aux utilisateurs qui ont plus de 10 tâches? 🤔 Voyez comment Metricalp aide à prendre des mesures marketing.
D'accord, nous sommes presque prêts, mais nous devons définir ces propriétés personnalisées pour les événements personnalisés dans le tableau de bord Metricalp. Allons dans les paramètres du tracker sur le tableau de bord Metricalp pour le faire :
Voici tous les événements :
Attachons des propriétés personnalisées à ces événements :
Eh bien, nous avons ajouté tous nos événements personnalisés. Nous avons attaché 'day_time' comme alias de custom_prop1 à tous les événements. Nous avons également attaché placement comme custom_prop2 et current_todos_count comme custom_prop3 à l'événement upgrade_pro_navigator. Nous avons également attaché current_todos_count à l'événement upgrade_pro_click comme custom_prop2. Nous sommes prêts 🚀
Vérifions notre tableau de bord :
Eh bien, nous avons des événements screen_view avec des informations sur la localisation, le chemin, la langue de l'utilisateur, la version du système d'exploitation, la version de l'application, mais en plus nous avons des événements personnalisés pour l'ajout/mise à jour/suppression de todo et les actions de mise à niveau. Nous avons des informations sur l'heure de la journée, des informations sur le nombre actuel de todos et des informations sur l'emplacement du navigateur de mise à niveau. Ce qui est génial. Nous recueillons des données très précieuses comme dans un vrai test A/B. Nous pouvons décider d'afficher le bouton de mise à niveau aux utilisateurs, par exemple, qui ont plus de 10 todos et nous pouvons décider d'afficher des réductions la nuit. Nous pouvons supprimer le bouton de navigation de mise à niveau inférieur et ne garder que le supérieur. Je veux dire, les données détermineront notre direction marketing comme dans une vraie entreprise.
Eh bien, tout est facile avec ces puissantes technologies comme React Native, Expo et bien sûr Metricalp 💜. Merci si vous avez lu jusqu'ici, n'hésitez pas à nous contacter si vous en avez besoin 🤝