Creare un'app React Native reale da zero con Expo e Metricalp (2024)
Sappiamo che non ti piace leggere documentazioni e boilerplate. Quindi, abbiamo preparato un esempio reale per te. In questo post, creeremo un'app React Native da zero con Expo e Metricalp.
Introduzione
Stiamo per sviluppare un'applicazione ToDo in React Native. L'utente può aggiungere, aggiornare e cancellare le attività. Ma, mentre lo consideriamo come un'applicazione del mondo reale, avremo alcuni fastidiosi annunci sullo schermo 😬. L'utente può passare a un account PRO per eliminare gli annunci. Quindi, nella nostra app ci sarà una seconda schermata /upgrade-pro. In questa schermata avremo un pulsante per l'upgrade a PRO. Utilizzeremo Metricalp per tracciare i comportamenti degli utenti per analizzare la nostra app. Fidati di me, sarà divertente.
Per renderla come un'applicazione del mondo reale, utilizzeremo alcune fantastiche librerie nella nostra app come il Zustand store (per gestire lo stato) e MMKV store (per memorizzare i dati in locale in modo velocissimo). Ecco alcuni screenshot finali della nostra app:
Ok, basta parlare, costruiamola 🚀!
Configurazione del Progetto
Per prima cosa, dobbiamo creare un nuovo progetto React Native con Expo:
npx create-expo-app@latest
In questo esempio, utilizzeremo principalmente il simulatore iOS. Quindi, è necessario avere Xcode installato sul tuo computer. Se non hai Xcode, puoi installarlo dall'App Store. Inoltre, installeremo watchman per lavorare correttamente nel simulatore:
brew update
brew install watchman
Successivamente installeremo expo dev client per lavorare con la nostra app nel simulatore:
npx expo install expo-dev-client
Assicurati di avere installato l'ultima versione del simulatore. Puoi controllarlo in XCode -> Preferences -> Components.
Eseguiamo la nostra app per la prima volta, all'interno della cartella della nuova applicazione esegui:
npx expo run:ios
Installare le Librerie
Ora, installeremo alcune librerie per rendere la nostra vita più facile. Per prima cosa, installeremo la libreria react-native-mmkv store di mrousavy. Funziona sulla nuova architettura di React Native, quindi puoi leggere/scrivere nel locale in modo sincronizzato e veloce:
npx expo install react-native-mmkv
A questo punto dobbiamo anche eseguire expo prebuild
npx expo prebuild
Per tracciare i nostri utenti e le loro attività utilizzeremo UUIDs creati casualmente. Installeremo la libreria crypto di expo per generare questi UUIDs:
npx expo install expo-crypto
Conserveremo le attività nel Zustand store (una libreria di gestione dello stato), quindi dobbiamo installarla:
yarn add zustand
Infine, installeremo Metricalp per tracciare i comportamenti degli utenti nella nostra app (mentre scriviamo questo articolo l'ultima versione è 1.0.12 ma installa sempre l'ultima):
yarn add @metricalp/react-native
Struttura della App
Qui la struttura finale della app. Spiegheremo tutti i file utilizzati qui sotto:
Iniziamo con alcune basi. Prima crea una cartella chiamata 'utils' nella radice del progetto.
Successivamente crea un file chiamato zustand-store.ts nella cartella utils. Questo file sarà utilizzato per gestire lo stato nella app:
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([]);
};
Fondamentalmente abbiamo definito un Zustand store con le attività. Abbiamo alcune funzioni per aggiungere, aggiornare, eliminare e ripristinare le attività. Stiamo usando mmkv-store per memorizzare le attività nel locale. Stiamo anche aggiornando questa copia del locale ad ogni aggiornamento dello stato. Generiamo UUIDs casuali per ogni nuova attività.
Ora generiamo un file chiamato mmkv-store.ts nella cartella utils:
import { MMKV } from 'react-native-mmkv';
export const storage = new MMKV();
Ora generiamo un file chiamato constants.ts nella cartella utils. Qui manterremo le costanti:
export const METR_UUID_KEY = 'metr.uuid';
export const USER_TODOS_KEY = 'user.todos';
export const METRICALP_TID = 'mam48'; // Sostituisci con il tuo TID di Metricalp
export const SCREENS = {
INDEX: 'index',
UPGRADE_PRO: 'upgrade-pro',
};
Infine, generiamo un file chiamato metricalp-events.ts nella cartella utils. Terremo i nostri eventi personalizzati per Metricalp in questo file:
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',
};
Tracceremo i clic di modifica ed eliminazione delle attività, il clic per aggiungere attività, il clic per aggiornare le attività e i clic per aggiornare a PRO nella nostra app. Inoltre, vogliamo tracciare come l'utente è arrivato alla schermata di aggiornamento, quindi abbiamo un altro evento upgrade_pro_navigator che attiveremo ogni volta che un utente naviga alla schermata di aggiornamento. Aggiungeremo una proprietà a questo evento per conservare il modo in cui l'utente è arrivato all'aggiornamento. Inoltre, registreremo quante attività aveva l'utente quando ha deciso di aggiornare. Perché questa potrebbe essere una metrica importante (il team di marketing ha detto questo). Inoltre, vogliamo registrare l'ora del giorno (mattina, pomeriggio, sera o notte) per tutti questi eventi. Vogliamo capire il comportamento dell'utente, forse tendono a usare l'app di notte ma hanno più probabilità di aggiornare al mattino? Ancora una volta, il team di marketing ha chiesto questi dati. In Metricalp possiamo conservare qualsiasi cosa, possiamo avere scenari illimitati 🤝
Ora, controlliamo il file app/_layout.tsx, che è il nostro file di layout principale, inizializzeremo Metricalp e la nostra app qui:
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';
// Impedisce che lo splash screen si nasconda automaticamente prima che il caricamento delle risorse sia completo.
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: 'Italian-IT',
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>
);
}
Stiamo inizializzando le nostre attività dal locale prima (mmkv store). Poi inizializziamo Metricalp con alcune informazioni di base come la piattaforma, il nome dell'app, la lingua, il sistema operativo, l'UUID e il TID. Stiamo anche tracciando i cambiamenti di stato dell'app per capire quando l'app va in background o in primo piano. Stiamo anche tracciando le visualizzazioni delle schermate nella nostra app, stiamo usando l'hook usePathname per ottenere il nome del percorso attuale della nostra app. Nascondiamo lo splash screen quando tutte queste inizializzazioni sono completate.
Stiamo generando un UUID per utente e mantenendolo nel locale. Metricalp usa questo UUID per identificare l'utente e contare le visualizzazioni/eventi unici. Puoi anche usare user_id ecc. se lo desideri.
Quando l'app va in background o in stato inattivo, generiamo eventi appLeaveEvent dalla libreria Metricalp. Questo ci aiuta a mantenere le durate delle visualizzazioni delle schermate ecc.
Abbiamo generato un hook personalizzato useDayTime all'interno della cartella hooks (hooks/useDayTime.ts) per ottenere l'ora del giorno dell'utente:
export function useDayTime() {
const now = new Date();
const hours = now.getHours();
if (hours >= 6 && hours < 12) {
return 'mattina';
}
if (hours >= 12 && hours < 18) {
return 'pomeriggio';
}
if (hours >= 18 && hours < 24) {
return 'sera';
}
return 'notte';
}
Ora modifichiamo il file (tabs)/_layout.tsx per avere il menu della scheda di aggiornamento e attivare l'evento upgrade_pro_navigator quando si fa clic su questo 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: 'Home',
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>
);
}
Vedi che stiamo anche aggiungendo le proprietà placement, current_todos_count e day_time al nostro evento personalizzato. Che facile e fantastico, vero?
Ora modifica il file (tabs)/index.tsx per avere la schermata principale della nostra app:
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}>Modifica</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleDeleteTask(item.id)}>
<Text style={styles.deleteButton}>Elimina</Text>
</TouchableOpacity>
</View>
</View>
);
const renderAds = (adsPlace: 'top' | 'bottom') => {
return (
<>
<View style={styles.adsContainer}>
<Text style={styles.adsText}>Ecco alcuni annunci</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}>
Passa a Pro ora per un'esperienza senza annunci
</Text>
</TouchableOpacity>
</>
);
};
return (
<View style={styles.container}>
<Text style={styles.title}>App Lista di Cose da Fare</Text>
{renderAds('top')}
<TextInput
style={styles.input}
placeholder="Inserisci una Cosa da Fare"
value={todoInput}
onChangeText={(text) => setTodoInput(text)}
/>
<TouchableOpacity style={styles.addButton} onPress={handleAddTask}>
<Text style={styles.addButtonText}>
{editId ? 'Aggiorna Cosa da Fare' : 'Aggiungi Cosa da Fare'}
</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',
},
});
Quindi abbiamo un'app ToDo standard. Possiamo aggiungere/modificare/eliminare cose da fare. Abbiamo annunci fastidiosi e pulsanti di aggiornamento accanto agli annunci.
Stiamo tracciando ogni clic per aggiungere/modificare/eliminare attraverso Metricalp e stiamo attaccando la proprietà day_time a tutti questi eventi.
Ma stiamo anche aggiungendo le proprietà placement e current_todos_count all'evento upgrade_pro_navigator. Perché sono importanti per noi dal punto di vista del marketing.
Infine, crea il file (tabs)/upgrade-pro.tsx per avere la nostra schermata di aggiornamento 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}>App Lista di Cose da Fare</Text>
<Text>
Non vedrai mai annunci nell'applicazione quando aggiorni a PRO.
</Text>
<TouchableOpacity
style={styles.upgradeButton}
onPress={() => {
Metricalp.customEvent(MetricalpCustomEvents.UPGRADE_PRO_CLICK, {
day_time: dayTime,
current_todos_count: useZustandStore.getState().todos.length,
});
alert('Aggiornato a Pro ora');
}}
>
<Text style={styles.upgradeButtonText}>Passa a Pro ora</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',
},
});
Qui stiamo generando un evento personalizzato upgrade_pro_click quando l'utente clicca sul pulsante di aggiornamento. Stiamo anche aggiungendo le proprietà day_time e current_todos_count a questo evento. Mostriamo anche un avviso all'utente sul fatto che ha aggiornato.
current_todos_count può essere importante perché possiamo misurare quando un utente decide di aggiornare in base a quanti todos ha. Forse possiamo mostrare uno sconto agli utenti che hanno più di 10 todos? 🤔 Guarda come Metricalp aiuta a prendere azioni di marketing.
Ok, siamo quasi pronti ma dobbiamo definire queste proprietà personalizzate agli eventi personalizzati nel dashboard di Metricalp. Andiamo alle impostazioni del tracker nel dashboard di Metricalp per farlo:
Ecco tutti gli eventi:
Aggiungiamo proprietà personalizzate a questi eventi:
Bene, abbiamo aggiunto tutti i nostri eventi personalizzati. Abbiamo collegato 'day_time' come alias custom_prop1 a tutti gli eventi. Abbiamo anche collegato placement come custom_prop2 e current_todos_count come custom_prop3 all'evento upgrade_pro_navigator. Abbiamo anche collegato current_todos_count all'evento upgrade_pro_click come custom_prop2. Siamo pronti 🚀
Controlliamo il nostro dashboard:
Quindi, abbiamo eventi screen_view con informazioni sulla posizione, percorso, lingua dell'utente, versione del sistema operativo, versione dell'app, ma inoltre abbiamo eventi personalizzati per l'aggiunta/aggiornamento/eliminazione di todo e le azioni di aggiornamento. Abbiamo informazioni sul momento della giornata, informazioni sul numero attuale di todo e informazioni sulla posizione del navigatore di aggiornamento. Il che è fantastico. Stiamo raccogliendo dati molto preziosi come in un vero test A/B. Possiamo decidere di mostrare il pulsante di aggiornamento agli utenti che, ad esempio, hanno più di 10 todos e possiamo decidere di mostrare sconti nelle ore notturne. Possiamo rimuovere il pulsante di navigazione dell'aggiornamento in basso e mantenere solo quello superiore. Intendo dire che i dati determineranno la nostra direzione di marketing come in un vero business.
Beh, tutto è facile con queste potenti tecnologie come React Native, Expo e, naturalmente, Metricalp 💜. Grazie se hai letto fino a qui, non esitare a contattarci se hai domande 🤝