Esta guía explica en detalle cómo funciona el soporte offline en el adaptador de React Native para Pubflow y cómo puedes aprovecharlo en tus aplicaciones.
- Introducción
- Detección de Conectividad
- Cola de Mutaciones Offline
- Persistencia de Datos
- Indicadores de Estado Offline
- Ejemplos Prácticos
- Configuración Avanzada
- Mejores Prácticas
El soporte offline en Pubflow para React Native permite que tu aplicación siga funcionando incluso cuando el dispositivo no tiene conexión a Internet. Las principales características incluyen:
- Detección automática de conectividad: Monitoreo del estado de la conexión
- Cola de mutaciones offline: Almacenamiento de operaciones para ejecutarlas cuando se recupere la conexión
- Persistencia de datos: Almacenamiento local de datos para acceso offline
- Indicadores visuales: Componentes para mostrar el estado de la conexión
Pubflow utiliza un wrapper alrededor de @react-native-community/netinfo para detectar el estado de la conexión. Este wrapper funciona incluso si el paquete no está instalado, proporcionando valores predeterminados.
import { NetInfo } from '@pubflow/react-native';
// Verificar si el dispositivo está conectado
const checkConnection = async () => {
const isConnected = await NetInfo.isConnected();
console.log(`El dispositivo está ${isConnected ? 'conectado' : 'desconectado'}`);
};
// Escuchar cambios en la conectividad
const unsubscribe = NetInfo.addEventListener(state => {
console.log(`Estado de la conexión: ${state.isConnected ? 'conectado' : 'desconectado'}`);
if (state.isConnected) {
console.log('Conexión recuperada, sincronizando datos...');
}
});
// Importante: cancelar la suscripción cuando el componente se desmonte
useEffect(() => {
return () => {
unsubscribe();
};
}, []);Todos los hooks de Pubflow (useBridgeQuery, useBridgeMutation, useBridgeCrud) integran automáticamente la detección de conectividad y exponen el estado a través de la propiedad isOffline:
const {
items,
loading,
error,
isOffline
} = useBridgeCrud({
entityConfig: {
endpoint: 'products'
}
});
// Mostrar un mensaje cuando está offline
{isOffline && (
<Text style={styles.offlineMessage}>
Trabajando en modo offline
</Text>
)}La característica más potente del soporte offline es la cola de mutaciones, que permite a los usuarios realizar cambios incluso cuando están desconectados.
- Cuando el usuario realiza una operación (crear, actualizar, eliminar) mientras está offline, la operación se almacena en una cola en memoria.
- Se muestra una notificación al usuario indicando que la operación se realizará cuando se recupere la conexión.
- Cuando el dispositivo vuelve a estar online, las operaciones en cola se ejecutan automáticamente en el orden en que fueron agregadas.
- Si una operación falla, se continúa con la siguiente (este comportamiento es configurable).
- El número de operaciones en cola está disponible a través de la propiedad
queuedMutations.
La cola de mutaciones se puede configurar a nivel global o por operación:
// Configuración global en useBridgeCrud
const crudHook = useBridgeCrud({
entityConfig: {
endpoint: 'products'
},
offlineConfig: {
queueMutations: true, // Habilitar cola de mutaciones
showOfflineAlerts: true // Mostrar alertas cuando se encolan operaciones
}
});
// Configuración por operación
const handleCreate = async () => {
try {
await crudHook.createItem(newProduct, {
queueWhenOffline: true, // Encolar si está offline
showAlerts: true // Mostrar alertas
});
} catch (error) {
console.error('Error:', error);
}
};Puedes monitorear el estado de la cola de mutaciones:
const {
createItem,
updateItem,
deleteItem,
queuedMutations,
isOffline
} = useBridgeCrud({
entityConfig: {
endpoint: 'products'
}
});
// Mostrar indicador de operaciones pendientes
{queuedMutations > 0 && (
<View style={styles.queueBadge}>
<Text style={styles.queueText}>{queuedMutations}</Text>
</View>
)}
// Mostrar mensaje detallado
{queuedMutations > 0 && (
<Text>
{queuedMutations} {queuedMutations === 1 ? 'operación pendiente' : 'operaciones pendientes'}
de sincronización
</Text>
)}import React, { useState } from 'react';
import { View, Text, TextInput, Button, StyleSheet, Alert } from 'react-native';
import { useBridgeCrud, OfflineIndicator } from '@pubflow/react-native';
export default function ProductsScreen() {
const [name, setName] = useState('');
const [price, setPrice] = useState('');
const {
items: products,
createItem,
deleteItem,
isOffline,
queuedMutations
} = useBridgeCrud({
entityConfig: {
endpoint: 'products'
},
offlineConfig: {
queueMutations: true,
showOfflineAlerts: true
},
successMessages: {
create: 'Producto creado correctamente',
delete: 'Producto eliminado correctamente'
}
});
const handleAddProduct = async () => {
if (!name || !price) {
Alert.alert('Error', 'Por favor completa todos los campos');
return;
}
try {
await createItem({
name,
price: parseFloat(price)
});
// Limpiar formulario
setName('');
setPrice('');
} catch (error) {
console.error('Error al crear producto:', error);
}
};
return (
<View style={styles.container}>
<OfflineIndicator />
{/* Indicador de operaciones pendientes */}
{queuedMutations > 0 && (
<View style={styles.queueContainer}>
<Text style={styles.queueText}>
{queuedMutations} {queuedMutations === 1 ? 'operación pendiente' : 'operaciones pendientes'}
de sincronización
</Text>
</View>
)}
{/* Formulario */}
<View style={styles.form}>
<Text style={styles.label}>Nombre del producto</Text>
<TextInput
style={styles.input}
value={name}
onChangeText={setName}
placeholder="Ingrese nombre"
/>
<Text style={styles.label}>Precio</Text>
<TextInput
style={styles.input}
value={price}
onChangeText={setPrice}
placeholder="Ingrese precio"
keyboardType="numeric"
/>
<Button
title={isOffline ? "Añadir producto (offline)" : "Añadir producto"}
onPress={handleAddProduct}
/>
</View>
{/* Lista de productos */}
<Text style={styles.heading}>Productos ({products.length})</Text>
{products.map(product => (
<View key={product.id} style={styles.productItem}>
<View>
<Text style={styles.productName}>{product.name}</Text>
<Text style={styles.productPrice}>${product.price}</Text>
</View>
<Button
title="Eliminar"
color="#ff6b6b"
onPress={() => deleteItem(product.id)}
/>
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
},
queueContainer: {
backgroundColor: '#fff3cd',
padding: 10,
borderRadius: 5,
marginBottom: 15,
},
queueText: {
color: '#856404',
textAlign: 'center',
},
form: {
marginBottom: 20,
padding: 15,
backgroundColor: '#f8f9fa',
borderRadius: 5,
},
label: {
marginBottom: 5,
fontWeight: 'bold',
},
input: {
height: 40,
borderWidth: 1,
borderColor: '#ced4da',
borderRadius: 5,
marginBottom: 15,
paddingHorizontal: 10,
backgroundColor: '#fff',
},
heading: {
fontSize: 18,
fontWeight: 'bold',
marginBottom: 10,
},
productItem: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 15,
backgroundColor: '#fff',
borderRadius: 5,
marginBottom: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 1,
elevation: 2,
},
productName: {
fontSize: 16,
fontWeight: 'bold',
},
productPrice: {
color: '#28a745',
fontWeight: 'bold',
},
});Además de encolar mutaciones, Pubflow también puede almacenar datos para acceso offline:
- Caché automática: Los datos obtenidos con
useBridgeQueryse almacenan en caché automáticamente. - Almacenamiento manual: Puedes almacenar datos manualmente usando
SecureStorageAdapter.
import { SecureStorageAdapter } from '@pubflow/react-native';
// Crear un adaptador de almacenamiento
const storage = new SecureStorageAdapter({
prefix: 'myapp_offline'
});
// Guardar datos para acceso offline
const saveOfflineData = async (key, data) => {
await storage.setItem(key, JSON.stringify(data));
};
// Recuperar datos offline
const getOfflineData = async (key) => {
const data = await storage.getItem(key);
return data ? JSON.parse(data) : null;
};
// Ejemplo de uso
const cacheProductsForOffline = async (products) => {
await saveOfflineData('products_cache', products);
console.log('Productos guardados para acceso offline');
};
const loadOfflineProducts = async () => {
const cachedProducts = await getOfflineData('products_cache');
if (cachedProducts) {
console.log('Cargados', cachedProducts.length, 'productos desde caché offline');
return cachedProducts;
}
return [];
};Pubflow proporciona componentes para mostrar el estado de la conexión:
import { OfflineIndicator } from '@pubflow/react-native';
// Uso básico
<OfflineIndicator />
// Personalizado
<OfflineIndicator
offlineText="Sin conexión a Internet"
onlineText="Conexión restaurada"
onlineDuration={5000}
style={{ backgroundColor: '#d32f2f' }}
offlineTextStyle={{ color: 'white', fontWeight: 'bold' }}
/>También puedes crear tus propios indicadores utilizando el estado isOffline:
import { useBridgeCrud } from '@pubflow/react-native';
function MyComponent() {
const { isOffline } = useBridgeCrud({
entityConfig: {
endpoint: 'products'
}
});
return (
<View style={styles.container}>
{isOffline && (
<View style={styles.offlineBanner}>
<Icon name="wifi-off" size={20} color="white" />
<Text style={styles.offlineText}>
Modo offline activado
</Text>
</View>
)}
{/* Resto del componente */}
</View>
);
}Puedes personalizar el comportamiento de la cola de mutaciones:
// En useBridgeMutation
const {
mutate,
isLoading,
error,
isOffline,
queuedMutations
} = useBridgeMutation(
productService,
'create',
{
// Opciones generales
successMessage: 'Producto creado correctamente',
errorMessage: 'Error al crear el producto',
// Opciones de offline
queueWhenOffline: true, // Encolar cuando esté offline
showAlerts: true, // Mostrar alertas
// Callbacks
onSuccess: (data) => {
console.log('Operación exitosa:', data);
},
onError: (error) => {
console.error('Error en operación:', error);
}
}
);Por defecto, las operaciones se ejecutan en el orden en que fueron encoladas (FIFO). Si necesitas priorizar ciertas operaciones, puedes implementar tu propia lógica de priorización:
// Ejemplo conceptual (no incluido directamente en la API)
const priorityMutation = async (data, priority = 'normal') => {
// Verificar si está offline
const isConnected = await NetInfo.isConnected();
if (!isConnected) {
// Encolar con prioridad
enqueueOperation({
type: 'create',
data,
priority: priority === 'high' ? 1 : priority === 'low' ? 3 : 2
});
return;
}
// Si está online, ejecutar normalmente
return await createItem(data);
};-
Siempre verifica el estado offline: Muestra indicadores claros cuando la aplicación está en modo offline.
-
Proporciona feedback al usuario: Informa al usuario cuando las operaciones se encolan y cuando se completan.
-
Maneja conflictos: Implementa estrategias para manejar conflictos cuando las operaciones encoladas se ejecutan (por ejemplo, si un elemento fue eliminado por otro usuario).
-
Limita operaciones offline: Algunas operaciones pueden no ser adecuadas para el modo offline. Considera deshabilitar funcionalidades complejas cuando no hay conexión.
-
Prueba el modo offline: Prueba exhaustivamente el comportamiento de tu aplicación en modo offline y durante transiciones entre estados de conectividad.
-
Optimiza el almacenamiento: No almacenes grandes cantidades de datos localmente, ya que puede afectar el rendimiento de la aplicación.
-
Implementa sincronización bidireccional: Para aplicaciones con uso intensivo offline, considera implementar sincronización bidireccional con resolución de conflictos.
// Ejemplo de sincronización al recuperar conexión
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(async (state) => {
if (state.isConnected && previouslyOffline) {
// Sincronizar datos locales con el servidor
await syncLocalChanges();
// Obtener cambios del servidor
await fetchServerChanges();
}
setPreviouslyOffline(!state.isConnected);
});
return () => unsubscribe();
}, [previouslyOffline]);El soporte offline en Pubflow para React Native proporciona una experiencia de usuario fluida incluso cuando no hay conexión a Internet. La cola de mutaciones offline, combinada con la persistencia de datos y los indicadores visuales, permite a los usuarios seguir trabajando sin interrupciones, con la seguridad de que sus cambios se sincronizarán cuando se recupere la conexión.