Offline-Schlaftracking: React Native, WatermelonDB, Expo
Lernen Sie, eine stabile Schlaftracking-App mit React Native offline zu erstellen. Der Leitfaden zeigt WatermelonDB und eine robuste Sync-Strategie mit Expo.
Auf dieser Seite
In einer Welt, in der ständige Konnektivität die Norm ist, was geschieht, wenn sie nicht verfügbar ist? Für eine App, die etwas so Persönliches und Alltägliches wie den Schlaf aufzeichnen soll, ist Zuverlässigkeit von größter Bedeutung. Stellen Sie sich vor, Sie wachen nach einer erholsamen Nacht auf und möchten die Details erfassen, werden jedoch nur von einem Lade-Spinner begrüßt, weil die Internetverbindung schwach oder gar nicht vorhanden ist. Das ist eine frustrierende Nutzererfahrung, die sich mit einer Offline-First-Architektur vermeiden lässt.
In diesem Tutorial entwickeln wir eine Schlaf-Tracking-App mit React Native und Expo, die die Fähigkeit der Nutzer priorisiert, jederzeit mit ihren Daten zu interagieren. Wir nutzen die Leistungsfähigkeit von WatermelonDB, einem leistungsstarken reaktiven Datenbank-Framework, um Daten lokal auf dem Gerät zu speichern. Außerdem implementieren wir einen robusten Synchronisationsmechanismus, der die lokal gespeicherten Daten an einen zentralen Server überträgt, sobald eine Internetverbindung verfügbar ist.
Dieser Ansatz verbessert nicht nur das Nutzererlebnis, indem sich die App schneller und zuverlässiger anfühlt, sondern verringert auch die Abhängigkeit vom Server.
Voraussetzungen:
- Grundlegendes Verständnis von React Native und Expo.
- Node.js und npm/yarn installiert.
- Ein Expo-Go-Konto und die App auf Ihrem Gerät installiert (oder eine eingerichtete Simulatorumgebung).
- Ein Backend für die Synchronisation (wir stellen ein einfaches Node.js-/Express-Beispiel bereit).
Das Problem verstehen
Herkömmliche mobile Anwendungen sind für Datenoperationen häufig auf eine ständige Verbindung zu einem entfernten Server angewiesen. Das kann zu einem trägen und unzuverlässigen Nutzererlebnis führen, insbesondere in Gebieten mit schlechter Konnektivität. Ein Offline-First-Ansatz kehrt dieses Modell um, indem die lokale Datenbank als primäre Quelle der Wahrheit behandelt wird.
Herausforderungen eines naiven Offline-Ansatzes:
- Datenkonflikte: Was geschieht, wenn Daten auf mehreren Geräten offline geändert werden?
- Komplexität der Synchronisation: Wie synchronisieren Sie lokale Änderungen effizient mit einem entfernten Server, ohne Daten zu verlieren?
- Leistung: Wie gehen Sie mit großen Mengen lokaler Daten um, ohne die App zu verlangsamen?
WatermelonDB wurde entwickelt, um diese Herausforderungen direkt anzugehen. Es basiert auf SQLite, bietet damit eine solide und performante Grundlage, und seine Synchronisationsprimitive erleichtern es, über die Datensynchronisation nachzudenken und sie umzusetzen.
Voraussetzungen
Bevor wir beginnen, stellen Sie sicher, dass die folgenden Werkzeuge installiert und eingerichtet sind:
- Node.js (LTS-Version): https://nodejs.org/
- Expo CLI:
npm install -g expo-cli - Ein neues Expo-Projekt:
expo init offline-sleep-tracker(wählen Sie eine leere Vorlage) - Ein physisches Gerät mit der Expo-Go-App oder ein Emulator.
Schritt 1: WatermelonDB mit Expo einrichten
Obwohl WatermelonDB ein natives Modul ist, können wir es dank eines speziellen Plugins leicht in unseren verwalteten Expo-Workflow integrieren.
Was wir tun
Wir installieren WatermelonDB und seine Abhängigkeiten und konfigurieren anschließend das Expo-Plugin, damit es die native Einrichtung für uns übernimmt.
Umsetzung
-
Abhängigkeiten installieren:
bash npx expo install @nozbe/watermelondb @babel/plugin-proposal-decorators npx expo install @morrowdigital/watermelondb-expo-plugin expo-build-properties -
babel.config.jskonfigurieren:Fügen Sie das Plugin
@babel/plugin-proposal-decoratorshinzu. Stellen Sie sicher, dass es als erstes Plugin aufgeführt ist.javascript // babel.config.js module.exports = function (api) { api.cache(true); return { presets: ['babel-preset-expo'], plugins: [ ['@babel/plugin-proposal-decorators', { 'legacy': true }], // ... other plugins ], }; }; -
app.jsonkonfigurieren:Fügen Sie das WatermelonDB-Plugin zu Ihrer Datei
app.jsonhinzu.json // app.json { "expo": { // ... other settings "plugins": [ "@morrowdigital/watermelondb-expo-plugin" ] } }
So funktioniert es
Das @morrowdigital/watermelondb-expo-plugin übernimmt während des Build-Prozesses automatisch die von WatermelonDB benötigte native Konfiguration und ermöglicht uns so die Nutzung innerhalb eines verwalteten Expo-Projekts. Das Plugin expo-build-properties erlaubt eine weitergehende Anpassung nativer Builds.
Schritt 2: Datenschema und Modelle definieren
Nun zum Kern unserer lokalen Datenbank: Schema und Modelle. Wir definieren die Struktur unserer Schlafdaten.
Was wir tun
Wir erstellen ein Schema, das eine Tabelle sleep_sessions definiert, sowie ein entsprechendes Modell SleepSession, um damit zu interagieren.
Umsetzung
-
Ein Verzeichnis
dberstellen:Erstellen Sie im Stammverzeichnis Ihres Projekts einen Ordner
db. Darin liegen alle datenbankbezogenen Dateien. -
Das Schema definieren (
db/schema.js):javascript // db/schema.js import { appSchema, tableSchema } from '@nozbe/watermelondb'; export default appSchema({ version: 1, tables: [ tableSchema({ name: 'sleep_sessions', columns: [ { name: 'started_at', type: 'number' }, { name: 'ended_at', type: 'number', isOptional: true }, { name: 'quality', type: 'number', isOptional: true }, { name: 'notes', type: 'string', isOptional: true }, { name: 'created_at', type: 'number' }, { name: 'updated_at', type: 'number' }, ], }), ], }); -
Das Modell erstellen (
db/SleepSession.js):javascript // db/SleepSession.js import { Model } from '@nozbe/watermelondb'; import { field, readonly, date } from '@nozbe/watermelondb/decorators'; export default class SleepSession extends Model { static table = 'sleep_sessions'; @field('started_at') startedAt; @field('ended_at') endedAt; @field('quality') quality; @field('notes') notes; @readonly @date('created_at') createdAt; @readonly @date('updated_at') updatedAt; }
So funktioniert es
Das Schema definiert die Tabellen und ihre Spalten in unserer SQLite-Datenbank. Das Modell ist eine JavaScript-Klasse, die einer Tabelle zugeordnet ist und es uns ermöglicht, Datensätze zu erstellen, zu lesen, zu aktualisieren und zu löschen, als wären sie Objekte. Die Dekoratoren @field verknüpfen Klasseneigenschaften mit Tabellenspalten.
Schritt 3: Die Datenbank initialisieren
Nachdem Schema und Modell definiert sind, initialisieren wir die Datenbank beim Start unserer App.
Was wir tun
Wir schaffen einen zentralen Einstiegspunkt für unsere Datenbank, sodass sie in der gesamten App zugänglich ist.
Umsetzung
-
db/index.jserstellen:javascript // db/index.js import { Database } from '@nozbe/watermelondb'; import SQLiteAdapter from '@nozbe/watermelondb/adapters/sqlite'; import schema from './schema'; import SleepSession from './SleepSession'; const adapter = new SQLiteAdapter({ schema, }); export const database = new Database({ adapter, modelClasses: [SleepSession], });
So funktioniert es
Wir erstellen einen SQLiteAdapter, der WatermelonDB mitteilt, wie es mit der zugrunde liegenden SQLite-Datenbank interagieren soll. Anschließend instanziieren wir die Klasse Database und übergeben unseren Adapter sowie ein Array unserer Modellklassen.
Schritt 4: Die Benutzeroberfläche erstellen und mit der Datenbank interagieren
Erstellen wir eine einfache Benutzeroberfläche, um Schlafsitzungen zu erfassen und anzuzeigen.
Was wir tun
Wir bauen eine React-Komponente, die Schlafsitzungen erstellen, anzeigen und aktualisieren kann. Wir verwenden die Higher-Order-Komponente withObservables von WatermelonDB, damit unsere Benutzeroberfläche auf Datenbankänderungen reagiert.
Umsetzung
-
Eine Komponente
SleepTrackererstellen:javascript // components/SleepTracker.js import React, { useState } from 'react'; import { View, Text, Button, FlatList, TextInput } from 'react-native'; import { withObservables } from '@nozbe/with-observables'; import { database } from '../db'; const SleepTracker = ({ sleepSessions }) => { const [isTracking, setIsTracking] = useState(false); const [currentSession, setCurrentSession] = useState(null); const [notes, setNotes] = useState(''); const handleStartTracking = async () => { await database.write(async () => { const newSession = await database.get('sleep_sessions').create(session => { session.startedAt = new Date().getTime(); }); setCurrentSession(newSession); setIsTracking(true); }); }; const handleEndTracking = async () => { await database.write(async () => { await currentSession.update(session => { session.endedAt = new Date().getTime(); session.notes = notes; }); setIsTracking(false); setCurrentSession(null); setNotes(''); }); }; const renderItem = ({ item }) => ( <View style={{ padding: 10, borderBottomWidth: 1, borderColor: '#ccc' }}> <Text>Started: {new Date(item.startedAt).toLocaleString()}</Text> {item.endedAt && <Text>Ended: {new Date(item.endedAt).toLocaleString()}</Text>} <Text>Notes: {item.notes}</Text> </View> ); return ( <View> {!isTracking ? ( <Button title="Start Sleep" onPress={handleStartTracking} /> ) : ( <View> <TextInput placeholder="Add notes..." value={notes} onChangeText={setNotes} /> <Button title="End Sleep" onPress={handleEndTracking} /> </View> )} <FlatList data={sleepSessions} renderItem={renderItem} keyExtractor={item => item.id} /> </View> ); }; const enhance = withObservables([], () => ({ sleepSessions: database.get('sleep_sessions').query().observe(), })); export default enhance(SleepTracker); -
Die Komponente in
App.jsverwenden:javascript // App.js import React from 'react'; import { SafeAreaView, StyleSheet } from 'react-native'; import SleepTracker from './components/SleepTracker'; export default function App() { return ( <SafeAreaView style={styles.container}> <SleepTracker /> </SafeAreaView> ); } const styles = StyleSheet.create({ container: { flex: 1, marginTop: 50, }, });
So funktioniert es
Die HOC withObservables abonniert unsere Komponente auf Änderungen in der Tabelle sleep_sessions. Wann immer eine neue Sitzung erstellt oder eine bestehende aktualisiert wird, rendert unsere Komponente automatisch mit den neuesten Daten neu. Alle Datenbankänderungen müssen in einen Block database.write() eingeschlossen werden.
Schritt 5: Synchronisation implementieren
Nun synchronisieren wir unsere Offline-Daten mit einem entfernten Server.
Was wir tun
Wir erstellen eine Funktion sync, die das Dienstprogramm synchronize von WatermelonDB verwendet, um Änderungen von einem Backend-Server abzurufen und dorthin zu übertragen.
Umsetzung
-
Ein einfaches Backend erstellen (z. B. in
server/index.js):Für dieses Tutorial verwenden wir einen einfachen In-Memory-Speicher. In einer realen Anwendung würden Sie eine persistente Datenbank wie PostgreSQL oder MongoDB einsetzen.
javascript // server/index.js const express = require('express'); const bodyParser = require('body-parser'); const app = express(); const port = 3000; app.use(bodyParser.json()); let sleepSessions = {}; app.get('/sync', (req, res) => { const lastPulledAt = parseInt(req.query.last_pulled_at || '0'); const changes = { sleep_sessions: { created: [], updated: Object.values(sleepSessions).filter(s => s.updated_at > lastPulledAt), deleted: [], }, }; res.json({ changes, timestamp: new Date().getTime() }); }); app.post('/sync', (req, res) => { const { changes } = req.body; const createdSessions = changes.sleep_sessions?.created || []; const updatedSessions = changes.sleep_sessions?.updated || []; createdSessions.forEach(session => { sleepSessions[session.id] = { ...session, updated_at: new Date().getTime() }; }); updatedSessions.forEach(session => { sleepSessions[session.id] = { ...session, updated_at: new Date().getTime() }; }); res.sendStatus(200); }); app.listen(port, () => { console.log(`Sync server listening at http://localhost:${port}`); }); -
Die Synchronisationsfunktion in Ihrer App erstellen (
db/sync.js):javascript // db/sync.js import { synchronize } from '@nozbe/watermelondb/sync'; import { database } from './index'; const SYNC_API_URL = 'http://<your-local-ip>:3000/sync'; export async function sync() { await synchronize({ database, pullChanges: async ({ lastPulledAt }) => { const response = await fetch( `${SYNC_API_URL}?last_pulled_at=${lastPulledAt || 0}` ); if (!response.ok) { throw new Error(await response.text()); } const { changes, timestamp } = await response.json(); return { changes, timestamp }; }, pushChanges: async ({ changes, lastPulledAt }) => { const response = await fetch( `${SYNC_API_URL}?last_pulled_at=${lastPulledAt || 0}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ changes }), } ); if (!response.ok) { throw new Error(await response.text()); } }, }); } -
Die Synchronisation in Ihrer App auslösen:
Sie können die Synchronisation manuell über eine Schaltfläche auslösen oder automatisch, wenn die App eine Internetverbindung erkennt.
javascript // In SleepTracker.js import { sync } from '../db/sync'; // ... inside the component <Button title="Sync Data" onPress={sync} />
So funktioniert es
Die Funktion synchronize steuert eine zweiphasige Synchronisation. Zuerst ruft pullChanges alle Datensätze vom Server ab, die seit dem Zeitstempel lastPulledAt erstellt oder aktualisiert wurden. Anschließend sendet pushChanges alle lokal erstellten, aktualisierten oder gelöschten Datensätze an den Server. WatermelonDB übernimmt die Konfliktlösung automatisch.
Fazit
Wir haben erfolgreich eine Offline-First-Schlaf-Tracking-Anwendung mit React Native, Expo und WatermelonDB entwickelt. Diese Architektur bietet ein überlegenes Nutzererlebnis, indem sie sicherstellt, dass die App unabhängig von den Netzwerkbedingungen stets funktionsfähig und reaktionsschnell bleibt.
Was wir erreicht haben:
- WatermelonDB in einem verwalteten Expo-Projekt eingerichtet.
- Ein Schema und Modelle für unsere Schlaf-Tracking-Daten definiert.
- Eine reaktive Benutzeroberfläche erstellt, die sich bei Datenbankänderungen automatisch aktualisiert.
- Eine robuste bidirektionale Synchronisation mit einem entfernten Server implementiert.
Nächste Schritte für Sie:
- Fügen Sie dem Synchronisationsprozess eine Nutzerauthentifizierung hinzu.
- Implementieren Sie ein anspruchsvolleres Backend mit einer persistenten Datenbank.
- Erkunden Sie fortgeschrittenere Funktionen von WatermelonDB, etwa Relationen.
- Verwenden Sie eine Bibliothek wie
NetInfo, um die Synchronisation automatisch auszulösen.
Ressourcen
- WatermelonDB-Dokumentation: https://watermelondb.dev/
- WatermelonDB-Expo-Plugin: https://github.com/morrowdigital/watermelondb-expo-plugin
- Expo-Dokumentation: https://docs.expo.dev/
Kurzreferenz
| Aspekt | Wichtige Information |
|---|---|
| Thema | Entwicklung |
| Relevanz | Patientenaufklärung und Bewusstsein |
| Maßnahme | Besprechen Sie dies mit Ihrem Gesundheitsdienstleister |
| Häufigkeit | Wie von Ihrem Behandlungsteam empfohlen |
| Evidenz | Basierend auf aktuellen klinischen Leitlinien |
Häufig gestellte Fragen
F: Woher weiß ich, ob das auf mich zutrifft? A: Besprechen Sie Ihre persönliche und familiäre Krankengeschichte mit Ihrem Gesundheitsdienstleister, um Ihre individuellen Risikofaktoren einzuschätzen und die geeigneten nächsten Schritte festzulegen.
F: Was sollte ich meinen Arzt fragen? A: Gute Fragen sind: „Was bedeuten meine Ergebnisse?“, „Welche Möglichkeiten habe ich?“, „Welche Änderungen des Lebensstils würden helfen?“ und „Wann sollte ich zur Nachsorge kommen?“
F: Gibt es Änderungen des Lebensstils, die helfen können? A: Die meisten Erkrankungen profitieren von grundlegenden Maßnahmen: ausgewogene Ernährung, regelmäßige körperliche Aktivität (150+ Minuten pro Woche), erholsamer Schlaf (7–9 Stunden), Stressbewältigung und der Verzicht auf Tabak.
Medizinischer Haftungsausschluss
Dieser Artikel dient ausschließlich Bildungszwecken und stellt keine medizinische Beratung dar. Konsultieren Sie stets einen qualifizierten Gesundheitsdienstleister, bevor Sie Entscheidungen über Ihre Gesundheit, Behandlung oder Versorgung treffen. Die individuellen Umstände unterscheiden sich, und was für eine Person angemessen ist, muss für eine andere nicht angemessen sein.
Wenn Sie einen medizinischen Notfall erleben, rufen Sie sofort 911 (oder Ihre örtliche Notrufnummer) an.