slogan3

Advies over Capacitor App Offline Functionaliteit en Data Synchronisatie

description

Met deze prompt krijg je inzicht in het bouwen van robuuste offline mogelijkheden voor je Capacitor app, inclusief effectieve data synchronisatie met je backend. Dit voorkomt dat gebruikers afhangen van constante internettoegang en verbetert de gebruikerservaring aanzienlijk. De prompt helpt ook bij het vermijden van veelvoorkomende synchronisatieproblemen en biedt praktische codevoorbeelden.

prompt

Help me bij het implementeren van offline functionaliteit in mijn Capacitor app. Beschrijf hoe ik data lokaal kan opslaan en synchroniseren met een backend zodra er weer internetverbinding is. Mijn app gebruikt de volgende opslagmethode: ...

try_prompt

generate_helper
disclaimerOnPageApi

ai_answers

provider: openai
model: gpt-4.1-nano
Natuurlijk! Ik help je graag met het implementeren van offline functionaliteit in je Capacitor app met SQLite en synchronisatie met je REST API. ### 1. Data lokaal opslaan met SQLite Aangezien je al SQLite gebruikt, kun je de volgende aanpak volgen: **Stap 1: Installeer benodigde plugins** ```bash npm install @capacitor-community/sqlite npx cap sync ``` **Stap 2: Initialiseer de database en maak tabellen** ```typescript import { SQLiteConnection, createConnection } from '@capacitor-community/sqlite'; const initDB = async () => { const connection = await createConnection('my_db', false, 'no-encryption', 1); await connection.open(); await connection.execute(` CREATE TABLE IF NOT EXISTS items ( id TEXT PRIMARY KEY, data TEXT, updatedAt INTEGER, synced INTEGER DEFAULT 0 ) `); return connection; }; ``` - `id`: unieke identifier, bijvoorbeeld UUID. - `data`: JSON-string van je data. - `updatedAt`: timestamp van laatste wijziging. - `synced`: 0 of 1, om te aangeven of de data gesynchroniseerd is. ### 2. Data toevoegen en bewerken Wanneer de gebruiker data wijzigt, sla je de data op en markeer je deze als niet gesynchroniseerd (`synced = 0`). ```typescript const saveItem = async (connection, item) => { const now = Date.now(); await connection.execute(` INSERT OR REPLACE INTO items (id, data, updatedAt, synced) VALUES (?, ?, ?, 0) `, [item.id, JSON.stringify(item.data), now]); }; ``` ### 3. Detectie van internetverbinding Gebruik de Capacitor Network plugin: ```typescript import { Network } from '@capacitor/network'; const checkNetworkStatus = async () => { const status = await Network.getStatus(); return status.connected; }; ``` ### 4. Synchronisatie met backend Wanneer er weer internet is, kun je de gegevens proberen te synchroniseren: ```typescript const syncData = async (connection) => { const { values } = await connection.query('SELECT * FROM items WHERE synced = 0'); for (const row of values) { const item = { id: row.id, data: JSON.parse(row.data), updatedAt: row.updatedAt }; try { // Verzend data naar backend await fetch('https://jouwdomein.com/api/items/' + item.id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(item.data) }); // Markeer als gesynchroniseerd await connection.execute(` UPDATE items SET synced = 1 WHERE id = ? `, [item.id]); } catch (error) { console.error('Fout bij synchronisatie:', error); } } }; ``` ### 5. Conflictoplossing Om synchronisatieconflicten te voorkomen: - **Gebruik timestamps** (`updatedAt`) om te bepalen welke data de meest recente is. - **Bij elke update**, stuur je de `updatedAt` mee. - **Bij synchronisatie**, vergelijk de `updatedAt` van de lokale data en de backend data: - Als de backend data nieuwer is, update je de lokale data. - Als de lokale data nieuwer is, stuur je deze naar de backend. **Voorbeeld conflictafhandeling:** ```typescript const resolveConflict = async (localItem, remoteItem) => { if (localItem.updatedAt > remoteItem.updatedAt) { // Lokale data is nieuwer, stuur naar backend await fetch('https://jouwdomein.com/api/items/' + localItem.id, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(localItem.data) }); // update local 'synced' status } else if (remoteItem.updatedAt > localItem.updatedAt) { // Backend data is nieuwer, update lokale data // en markeer als gesynchroniseerd } }; ``` ### 6. Periodieke synchronisatie Gebruik een timer of luister naar netwerkstatus: ```typescript Network.addListener('networkStatusChange', (status) => { if (status.connected) { syncData(connection); } }); ``` --- ### Samenvatting - Gebruik SQLite voor offline opslag. - Markeer gewijzigde data met `synced = 0`. - Detecteer internetverbinding en synchroniseer gegevens. - Vergelijk `updatedAt` timestamps om conflicten op te lossen. - Implementeer automatische synchronisatie bij het herstellen van de verbinding. Met deze methode zorg je voor robuuste offline functionaliteit en een goede synchronisatie met je backend. Mocht je meer hulp nodig hebben of codevoorbeelden voor specifieke gevallen, laat het gerust weten!
provider: deepseek
model: deepseek-chat
Zeker! Hier is een uitgebreide uitleg voor het implementeren van offline functionaliteit in je Capacitor app met SQLite en REST API synchronisatie. ## 1. SQLite Setup in Capacitor ### Installatie ```bash npm install @capacitor-community/sqlite npx cap sync ``` ### Basis Database Setup ```typescript import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite'; class DatabaseService { private db: any; private dbName = 'offline_app.db'; async initializeDatabase() { this.db = await CapacitorSQLite.createConnection({ database: this.dbName, version: 1 }); await this.db.open(); await this.createTables(); } private async createTables() { const queries = [ `CREATE TABLE IF NOT EXISTS sync_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, table_name TEXT NOT NULL, record_id TEXT NOT NULL, operation TEXT NOT NULL, data TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, is_synced BOOLEAN DEFAULT 0, sync_attempts INTEGER DEFAULT 0 )`, `CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL, last_sync DATETIME, local_updated BOOLEAN DEFAULT 0 )` ]; for (const query of queries) { await this.db.execute(query); } } } ``` ## 2. Offline Data Opslag en Sync Queue ### Sync Queue Management ```typescript class SyncService { async queueForSync(tableName: string, recordId: string, operation: 'CREATE' | 'UPDATE' | 'DELETE', data: any) { const query = ` INSERT INTO sync_data (table_name, record_id, operation, data) VALUES (?, ?, ?, ?) `; await this.db.execute(query, [ tableName, recordId, operation, JSON.stringify(data) ]); } async getPendingSyncs() { const query = ` SELECT * FROM sync_data WHERE is_synced = 0 AND sync_attempts < 3 ORDER BY timestamp ASC `; const result = await this.db.query(query); return result.values || []; } } ``` ## 3. Network Detection en Background Sync ### Network Status Monitoring ```typescript import { Network } from '@capacitor/network'; class NetworkService { private isOnline = false; constructor() { this.setupNetworkListener(); } private setupNetworkListener() { Network.addListener('networkStatusChange', (status) => { this.isOnline = status.connected; if (this.isOnline) { this.triggerSync(); } }); } async checkInitialStatus() { const status = await Network.getStatus(); this.isOnline = status.connected; return this.isOnline; } private async triggerSync() { // Wacht even om zeker te zijn van stabiele verbinding setTimeout(async () => { await this.syncPendingData(); }, 2000); } } ``` ## 4. Data Synchronisatie ### Sync Implementatie ```typescript class ApiSyncService { private apiBaseUrl = 'https://jouw-api.com'; async syncPendingData() { const pendingSyncs = await this.getPendingSyncs(); for (const syncItem of pendingSyncs) { try { await this.processSyncItem(syncItem); await this.markAsSynced(syncItem.id); } catch (error) { await this.incrementSyncAttempts(syncItem.id); console.error('Sync failed:', error); } } } private async processSyncItem(syncItem: any) { const data = JSON.parse(syncItem.data); switch (syncItem.operation) { case 'CREATE': await this.apiPost(`/${syncItem.table_name}`, data); break; case 'UPDATE': await this.apiPut(`/${syncItem.table_name}/${syncItem.record_id}`, data); break; case 'DELETE': await this.apiDelete(`/${syncItem.table_name}/${syncItem.record_id}`); break; } } private async apiPost(endpoint: string, data: any) { const response = await fetch(`${this.apiBaseUrl}${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) throw new Error('API request failed'); return response.json(); } // Vergelijkbare implementaties voor PUT en DELETE } ``` ## 5. Conflict Preventie Strategieën ### 1. Optimistic Locking met Versienummers ```typescript // Database tabel const optimisticLockingTable = ` CREATE TABLE IF NOT EXISTS products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, price DECIMAL, version INTEGER DEFAULT 1, last_updated DATETIME DEFAULT CURRENT_TIMESTAMP ) `; // Update met versie check async updateProductWithLock(id: number, newData: any, currentVersion: number) { const query = ` UPDATE products SET name = ?, price = ?, version = version + 1 WHERE id = ? AND version = ? `; const result = await this.db.execute(query, [ newData.name, newData.price, id, currentVersion ]); if (result.changes === 0) { throw new Error('Concurrent modification detected'); } } ``` ### 2. Last-Write-Wins met Timestamps ```typescript async syncWithServer(data: any) { const localTimestamp = new Date().toISOString(); // Voeg timestamp toe aan data const syncData = { ...data, last_modified: localTimestamp }; await this.queueForSync('products', data.id, 'UPDATE', syncData); } ``` ### 3. Operationele Transformations ```typescript class ConflictResolver { async resolveConflicts(localData: any, serverData: any) { const localTime = new Date(localData.last_modified); const serverTime = new Date(serverData.last_modified); // Last-write-wins strategie if (localTime > serverTime) { return { resolvedData: localData, winner: 'local' }; } else { return { resolvedData: serverData, winner: 'server' }; } } } ``` ## 6. Gebruikers Feedback en Status ### Sync Status Management ```typescript class SyncStatusService { private syncInProgress = false; private pendingItems = 0; async showSyncStatus() { const pending = await this.getPendingSyncCount(); this.pendingItems = pending; if (pending > 0 && this.syncInProgress) { this.showNotification(`${pending} items wachten op sync`); } } async getPendingSyncCount(): Promise<number> { const query = 'SELECT COUNT(*) as count FROM sync_data WHERE is_synced = 0'; const result = await this.db.query(query); return result.values[0].count; } } ``` ## 7. Tips voor Conflict Preventie ### 1. Gebruik UUID's voor Local IDs ```typescript import { v4 as uuidv4 } from 'uuid'; const generateLocalId = () => { return `local_${uuidv4()}`; }; ``` ### 2. Implementeer Exponential Backoff ```typescript async syncWithBackoff(syncItem: any) { const baseDelay = 1000; // 1 seconde const maxDelay = 30000; // 30 seconden let attempts = syncItem.sync_attempts; const delay = Math.min(baseDelay * Math.pow(2, attempts), maxDelay); await new Promise(resolve => setTimeout(resolve, delay)); } ``` ### 3. Batch Synchronisatie ```typescript async syncInBatches(batchSize = 10) { const pending = await this.getPendingSyncs(); for (let i = 0; i < pending.length; i += batchSize) { const batch = pending.slice(i, i + batchSize); await this.syncBatch(batch); } } ``` ## 8. Complete App Initialisatie ```typescript class OfflineApp { private dbService = new DatabaseService(); private syncService = new ApiSyncService(); private networkService = new NetworkService(); async initialize() { await this.dbService.initializeDatabase(); await this.networkService.checkInitialStatus(); // Start periodieke sync check setInterval(() => { if (this.networkService.isOnline) { this.syncService.syncPendingData(); } }, 30000); // Elke 30 seconden checken } } ``` Deze implementatie biedt een robuuste offline-first aanpak met goede conflict handling. Test altijd uitgebreid verschillende scenario's zoals langdurige offline periodes en gelijktijdige wijzigingen!