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
tags
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!