Imaginez que votre application soit utilisée par un détaillant mettant à jour les niveaux de stock, un représentant commercial accédant aux données client ou tout utilisateur envoyant des messages pendant une connectivité intermittente. Dans tous ces cas, la fonctionnalité hors ligne peut faire la différence entre une expérience utilisateur transparente et une expérience frustrante. C'est là qu'entre en jeu la réflexion hors ligne.
Une approche hors ligne garantit que votre application reste fonctionnelle même lorsque Internet n'est pas disponible. Des applications comme WhatsApp illustrent parfaitement ce concept. Lorsque vous envoyez un message hors ligne, il est stocké localement et automatiquement envoyé une fois la connexion rétablie. Cette expérience transparente est obtenue en exploitant le stockage local et en surveillant l’état du réseau. Que ce soit via une base de données ou la mémoire de l'appareil, l'application continue de fonctionner, synchronisant les données stockées avec le serveur lorsque la connectivité est à nouveau disponible.
Dans cet article, je vais vous guider dans la mise en œuvre de la prise en charge hors ligne dans les applications React Native à l'aide du stockage local, de la synchronisation de bases de données et des API Expo. Les avantages d'une approche hors ligne d'abord incluent :
Expo est un excellent framework pour le développement React Native car il résume de nombreuses configurations spécifiques à la plate-forme, vous permettant de vous concentrer sur la création de fonctionnalités. Dans cette section, nous explorerons comment implémenter la prise en charge hors ligne dans une simple application React Native en utilisant Expo, AsyncStorage pour le stockage local et NetInfo pour la détection de l'état du réseau.
Tout d’abord, commençons par créer un nouveau projet React Native basé sur Expo.
npx create-expo-app offline-first-app cd offline-first-app
Pour cet exemple, nous utiliserons deux bibliothèques de clés :
@react-native-async-storage/async-storage : Cette bibliothèque nous permettra de stocker des données sur l'appareil.
@react-native-community/netinfo : Cette bibliothèque nous aidera à détecter l'état du réseau, en déterminant si l'appareil est en ligne ou hors ligne.
Installez les packages nécessaires :
expo install @react-native-async-storage/async-storage @react-native-community/netinfo
Ensuite, nous allons créer une application simple qui récupère les données d'une API en ligne et les stocke localement pour les utiliser hors ligne. Nous commencerons par mettre en place la structure de base dans App.js :
import React, { useState, useEffect } from 'react'; import { StyleSheet, Text, View, Button, FlatList } from 'react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import NetInfo from '@react-native-community/netinfo'; const DATA_API = 'https://jsonplaceholder.typicode.com/posts'; export default function App() { const [data, setData] = useState([]); const [isOffline, setIsOffline] = useState(false); useEffect(() => { const loadData = async () => { // Check network status const netInfo = await NetInfo.fetch(); setIsOffline(!netInfo.isConnected); if (netInfo.isConnected) { // Fetch data from API when online try { const response = await fetch(DATA_API); const result = await response.json(); setData(result); // Cache the data for offline use await AsyncStorage.setItem('cachedData', JSON.stringify(result)); } catch (error) { console.error('Failed to fetch data:', error); } } else { // Load data from AsyncStorage when offline try { const cachedData = await AsyncStorage.getItem('cachedData'); if (cachedData) { setData(JSON.parse(cachedData)); } } catch (error) { console.error('Failed to load data from cache:', error); } } }; loadData(); }, []); return ( <View style={styles.container}> <Text style={styles.header}>Offline-First App</Text> <Text>Status: {isOffline ? 'Offline' : 'Online'}</Text> <FlatList data={data} keyExtractor={(item) => item.id.toString()} renderItem={({ item }) => ( <View style={styles.item}> <Text style={styles.title}>{item.title}</Text> </View> )} /> <Button title="Reload" onPress={() => loadData()} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, paddingTop: 50, paddingHorizontal: 20, backgroundColor: '#fff', }, header: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, item: { backgroundColor: '#f9c2ff', padding: 20, marginVertical: 8, }, title: { fontSize: 16, }, });
Comment ça marche ?
Détection de l'état du réseau : à l'aide de la bibliothèque NetInfo, nous vérifions si l'appareil est en ligne ou hors ligne. Si elle est en ligne, l'application récupère les données de l'API et les met en cache. Si l'appareil est hors ligne, l'application récupère les données mises en cache d'AsyncStorage.
Mise en cache des données : AsyncStorage nous permet de stocker les données récupérées de l'API pour un accès hors ligne. Ceci est essentiel pour rendre l'application fonctionnelle sans connexion Internet active.
Synchronisation des données : lorsque la connectivité est restaurée, l'application récupère de nouvelles données de l'API et met à jour le cache, garantissant ainsi aux utilisateurs de toujours disposer des informations les plus récentes lorsqu'ils sont en ligne.
Vous pouvez vous appuyer sur cette fonctionnalité de base en intégrant des fonctionnalités plus avancées, telles que :
Stratégies de synchronisation : certaines applications nécessitent des stratégies de synchronisation avancées, où des conflits peuvent survenir (par exemple, deux utilisateurs mettant à jour les mêmes données hors ligne). Des outils comme PouchDB ou Firebase peuvent aider à gérer la synchronisation des données en temps réel et la résolution des conflits.
Solutions de base de données : pour les applications plus complexes, vous souhaiterez peut-être utiliser une base de données locale comme Realm ou SQLite pour gérer des ensembles de données plus volumineux et des requêtes plus sophistiquées.
Mises à jour optimistes : dans certaines applications, en particulier celles dont le contenu est généré par les utilisateurs, comme les réseaux sociaux, il est courant d'autoriser les utilisateurs à créer, mettre à jour ou supprimer des données hors ligne. Vous pouvez mettre en œuvre des mises à jour optimistes, dans lesquelles les modifications sont apportées immédiatement à l'interface utilisateur et synchronisées avec le serveur lorsque l'application se reconnecte à Internet.
In an offline-first app, conflicts arise when multiple users update the same data while offline and their changes are later synced with the server once the app reconnects to the internet. Handling these conflicts is crucial to maintain data consistency and provide a smooth user experience.
There are different strategies for resolving such conflicts, including:
I have some examples here for you to check.
In this strategy, the most recent change (based on a timestamp) is accepted as the final value when syncing data. It is simple and works well for many applications, but it may lead to data loss if multiple users edit the same data.
Imagine you are building a note-taking app, if two users edit the same note while offline, the user who syncs their changes last will overwrite the previous user’s changes.
Let’s assume we have a local storage system (using AsyncStorage) and a remote server.
import AsyncStorage from '@react-native-async-storage/async-storage'; // Simulate syncing the note data with the server const syncNoteWithServer = async (localNote) => { try { // Fetch the server data const response = await fetch('https://api.example.com/note'); const serverNote = await response.json(); // Compare timestamps if (localNote.updatedAt > serverNote.updatedAt) { // Local version is newer, so overwrite the server await fetch('https://api.example.com/note', { method: 'PUT', body: JSON.stringify(localNote), headers: { 'Content-Type': 'application/json' }, }); } else { // Server version is newer, discard local changes await AsyncStorage.setItem('note', JSON.stringify(serverNote)); } } catch (error) { console.error('Sync failed:', error); } }; // Example usage const localNote = { content: 'This is an updated note.', updatedAt: Date.now(), // Timestamp of the last local update }; syncNoteWithServer(localNote);
In this example:
The app compares the updatedAt timestamp of the local note (stored offline) with the note stored on the server.
If the local note is newer, it overwrites the server version. Otherwise, it discards local changes and updates the app with the server version.
Pros:
Cons:
With manual conflict resolution, the user is prompted to resolve conflicts when multiple versions of the same data exist. This approach is more user-friendly in scenarios where every change is valuable and users need to decide which data to keep.
Here is a potential case: In a collaborative editing app, two users edit the same document while offline. Once both versions are synced, the user is prompted to choose which version to keep or merge.
import AsyncStorage from '@react-native-async-storage/async-storage'; import { Alert } from 'react-native'; // Simulate syncing the document with the server const syncDocumentWithServer = async (localDoc) => { try { // Fetch the server data const response = await fetch('https://api.example.com/document'); const serverDoc = await response.json(); if (localDoc.updatedAt !== serverDoc.updatedAt) { // Conflict detected, ask the user to resolve it Alert.alert( 'Document Conflict', 'Both you and another user have edited this document. Choose which version to keep.', [ { text: 'Keep Local', onPress: async () => { // Overwrite the server with local changes await fetch('https://api.example.com/document', { method: 'PUT', body: JSON.stringify(localDoc), headers: { 'Content-Type': 'application/json' }, }); }, }, { text: 'Keep Server', onPress: async () => { // Discard local changes and update the app with the server version await AsyncStorage.setItem('document', JSON.stringify(serverDoc)); }, }, ], ); } else { // No conflict, proceed with syncing await AsyncStorage.setItem('document', JSON.stringify(serverDoc)); } } catch (error) { console.error('Sync failed:', error); } }; // Example usage const localDoc = { content: 'This is my latest edit.', updatedAt: Date.now(), // Timestamp of the last local update }; syncDocumentWithServer(localDoc);
Here's what's happening
If the updatedAt timestamps differ between the local and server versions, the app alerts the user and asks them to choose which version to keep. The user can decide whether to keep the local or server version.
Pros:
Cons:
3. Operational Transformation (OT)
Operational Transformation is a more advanced technique used in real-time collaboration apps like Google Docs. It automatically merges conflicting changes by transforming operations in a way that preserves both sets of edits. OT allows multiple users to work on the same document simultaneously, and their changes are merged intelligently.
In a document editor app, two users edit different parts of a document. OT ensures that both sets of edits are applied without overwriting each other.
This implementation is a bit complex and require specialized libraries, such as ShareDB or Yjs. Here’s a basic pseudocode example of how OT works:
// Example of transforming two concurrent operations const operation1 = { type: 'insert', position: 5, value: 'Hello' }; // User 1 adds 'Hello' at position 5 const operation2 = { type: 'insert', position: 3, value: 'World' }; // User 2 adds 'World' at position 3 const transformOperations = (op1, op2) => { // If both operations modify different positions, no conflict if (op1.position !== op2.position) return [op1, op2]; // If operations conflict, adjust positions accordingly if (op1.position > op2.position) op1.position += op2.value.length; else op2.position += op1.value.length; return [op1, op2]; }; // Transform the operations to avoid conflicts const [transformedOp1, transformedOp2] = transformOperations(operation1, operation2);
The positions of the two conflicting operations are adjusted so that they can both be applied without overwriting each other.
Pros:
Cons:
Conclusion
Each conflict resolution strategy comes with its trade-offs. For simpler apps, Last Write Wins may suffice. However, for collaborative apps where user data is crucial, Manual Conflict Resolution or more advanced techniques like Operational Transformation might be necessary. Choosing the right strategy depends on the complexity of your app and the importance of the data being modified.
I plan to create a series of articles that dive deeper into the following key topics:
Optimistic UI Updates – We'll explore how to immediately reflect changes made while offline in the UI, giving users the impression that their actions were successful. This approach greatly improves the user experience.
Utilisation de Service Workers pour les applications Web – Si vous déployez votre application sur le Web via React Native Web, je vais vous expliquer comment les service Workers peuvent activer la mise en cache hors ligne et la synchronisation en arrière-plan pour Progressive Web. Applications (PWA). Cela garantit que les utilisateurs peuvent accéder aux ressources et aux données même lorsqu'ils sont hors ligne.
Cas d'utilisation réels des applications hors ligne – Je vais examiner de plus près la façon dont des applications comme Google Maps, Slack, Trello et Notion gèrent les scénarios hors ligne. En étudiant ces exemples, vous comprendrez mieux les applications pratiques des techniques hors ligne.
Test des capacités hors ligne – Nous aborderons l'importance de tester les fonctionnalités hors ligne et passerons en revue des outils tels que React Native Debugger, les outils Expo et le Network Link Conditioner (pour iOS) pour simuler les interruptions du réseau. Je vais également vous montrer comment écrire des tests à l'aide de bibliothèques telles que Jest et React Native Testing Library pour garantir que votre application se comporte correctement dans des conditions hors ligne.
Considérations sur les performances et le stockage – Les performances ne sont pas seulement une question de vitesse ; c'est aussi une question d'expérience utilisateur. Je discuterai des stratégies d'optimisation des performances en réduisant les données mises en cache et en mettant en œuvre des politiques d'expiration des données pour éviter de surcharger le stockage local.
Restez à l'écoute, les développeurs.
Merci d'avoir lu jusqu'au bout. J'aime vraiment documenter et partager mes apprentissages. Je prévois de créer davantage de contenu, notamment des didacticiels vidéo, que je partagerai sur Instagram et TikTok. Si vous êtes nouveau ici, je m'appelle Zidane Gimiga, un développeur de logiciels passionné par l'optimisation de l'expérience utilisateur. À mesure que la technologie s’intègre de plus en plus dans nos vies, il est essentiel de la rendre aussi simple et accessible que possible pour tous. Continuons à rechercher des solutions meilleures et plus conviviales.
Oh, je suis sur Github
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!