React NativeとWatermelonDB、Expoで構築するオフラインファースト睡眠トラッキング
インターネット接続없이動作する堅牢な睡眠トラッキングアプリをReact Nativeで構築する方法を学びます。ローカルデータ永続化のためのWatermelonDBの設定と、Expoでの堅牢な同期戦略の構築を解説します。
常時接続が標準となっている世界において、それが利用できない場合はどうなるでしょうか。睡眠のような個人的で日常的なものを追跡するアプリにとって、信頼性は最も重要です。素晴らしい睡眠の後に目覚め、詳細を記録しようとしたとき、弱いか存在しないインターネット接続のためにローディングスピナーが表示される経験を想像してみてください。これは、オフラインファーストアーキテクチャで回避できるフラストレーションのないユーザー体験です。
このチュートリアルでは、React NativeとExpoを使用して、ユーザーが常にデータと対話できる能力を最優先事項とする睡眠トラッキングアプリ構築します。ローカルデバイスにデータを保存するために、WatermelonDB(高性能リアクティブデータベースフレームワーク)の力を活用します。また、インターネット接続が利用可能になったら、ローカルに保存されたデータを中央サーバーにプッシュする堅牢な同期メカニズムも実装します。
このアプローチにより、アプリをより高速で信頼性の高いものにすることでユーザー体験が向上するだけでなく、サーバーへの依存も軽減されます。
前提条件:
- React NativeとExpoの基本的な理解。
- Node.jsおよびnpm/yarnがインストールされていること。
- Expo Goアカウントとデバイスにインストールされたアプリ(またはシミュレーターのセットアップ)。
- 同期先のバックエンド(シンプルなNode.js/Expressの例を提供します)。
問題の理解
標準的なモバイルアプリケーションは、データ操作のためにリモートサーバーへの常時接続に頼ることが多いです。これは特に接続性の悪い地域において、缓慢で信頼性の低いユーザー体験につながる可能性があります。オフラインファーストアプローチは、ローカルデータベースを主要な信頼できる情報源として扱うことで、このモデル翻转させます。
単純なオフラインアプローチの課題:
- データ競合: オフライン中に複数のデバイスでデータが変更された場合はどうなるか?
- 同期の複雑さ: データ損失なしにローカルでの変更をリモートサーバーと効率的に同期させるには?
- パフォーマンス: アプリを遅くすることなく大量のローカルデータをどのように処理するか?
WatermelonDBは、これらの課題に直接取り組むために設計されています。SQLiteの上に構築され、確実で高性能な基盤を提供し、同期のプリミティブによりデータ同期の推論と実装が容易になります。
前提条件
始める前に、以下のツールがインストールされセットアップされていることを確認してください:
- Node.js(LTSバージョン): https://nodejs.org/
- Expo CLI:
npm install -g expo-cli - 新しいExpoプロジェクト:
expo init offline-sleep-tracker(空のテンプレートを選択) - Expo Goアプリがインストールされた物理デバイスまたはエミュレーター。
ステップ1:ExpoでWatermelonDBをセットアップする
WatermelonDBはネイティブモジュールですが、専用のプラグイン 덕분에、マネージドExpoワークフローに簡単に統合できます。
目的
WatermelonDBとその依存関係をインストールし、Expoプラグインを構成してネイティブセットアップを処理してもらいます。
実装
-
依存関係をインストールする:
bash npx expo install @nozbe/watermelondb @babel/plugin-proposal-decorators npx expo install @morrowdigital/watermelondb-expo-plugin expo-build-properties -
babel.config.jsを構成する:@babel/plugin-proposal-decoratorsプラグインを追加します。 Listedされた最初のプラグインであることを確認してください。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.jsonを構成する:WatermelonDBプラグインを
app.jsonファイルに追加します。json // app.json { "expo": { // ... other settings "plugins": [ "@morrowdigital/watermelondb-expo-plugin" ] } }
動作の仕組み
@morrowdigital/watermelondb-expo-pluginは、ビルドプロセス中にWatermelonDBに必要なネイティブ設定を自動的に処理し、マネージドExpoプロジェクト内でその使用を可能にします。expo-build-propertiesプラグインは、ネイティブビルドのさらなるカスタマイズを可能にします。
ステップ2:データスキーマとモデルの定義
ローカルデータベースの中核であるスキーマとモデルを定義しましょう。睡眠データの構造を定義します。
目的
sleep_sessionsテーブルと、対応するSleepSessionモデルを定義し、それと対話するためのスキーマを作成します。
実装
-
dbディレクトリを作成する:プロジェクトルートの内部に
dbフォルダを作成します。これはすべてのデータベース関連のファイルを収容します。 -
スキーマを定義する(
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' }, ], }), ], }); -
モデルを作成する(
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; }
動作の仕組み
スキーマはSQLiteデータベース内のテーブルとそのカラムを定義します。モデルはテーブルにマッピングされたJavaScriptクラスであり、レコードを作成、読み取り、更新、削除をオブジェクトであるかのように操作できます。@fieldデコレータはクラスのプロパティをテーブルのカラムにリンクします。
ステップ3:データベースの初期化
スキーマとモデルを定義したので、アプリ起動時にデータベースを初期化しましょう。
目的
データベースへの一元的なエントリポイントを作成し、アプリ全体でアクセス可能にします。
実装
-
db/index.jsを作成する: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], });
動作の仕組み
SQLiteAdapterを作成し、WatermelonDBに基盤となるSQLiteデータベースと対話する方法を指示します。次に、アダプターとモデルクラスの配列を渡してDatabaseクラスをインスタンス化します。
ステップ4:UIの構築とデータベースとの対話
睡眠セッションを追跡し表示するシンプルなUIを作成しましょう。
目的
睡眠セッションを作成、表示、更新できるReactコンポーネントを構築します。WatermelonDBのwithObservables高次コンポーネントを使用して、UIをデータベースの変更にリアクティブにします。
実装
-
SleepTrackerコンポーネントを作成する: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); -
App.jsでコンポーネントを使用する: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, }, });
動作の仕組み
withObservables HOCはsleep_sessionsテーブルの変更にコンポーネントをサブスクライブします。新しいセッションが作成されるか、既存のセッションが更新されるたびに、コンポーネントは自動的に最新データで再レンダリングされます。すべてのデータベース変更はdatabase.write()ブロックでラップする必要があります。
ステップ5:同期の実装
では、オフラインデータをリモートサーバーと同期させましょう。
目的
WatermelonDBのsynchronizeユーティリティを使用して、バックエンドサーバーから変更をプルし、プッシュするsync関数を作成します。
実装
-
シンプルなバックエンドを作成する(例:
server/index.js):このチュートリアルでは、シンプルなメモリ内ストアを使用します。実際のアプリケーションでは、PostgreSQLやMongoDBなどの永続データベースを使用します。
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}`); }); -
アプリ内にsync関数を作成する(
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()); } }, }); } -
アプリ内で同期をトリガーする:
ボタンで同期を手動でトリガーするか、アプリがインターネット接続を検出したときに自動的にトリガーできます。
javascript // In SleepTracker.js import { sync } from '../db/sync'; // ... inside the component <Button title="Sync Data" onPress={sync} />
動作の仕組み
synchronize関数は2フェーズの同期を調整します。まず、pullChangesはlastPulledAtタイムスタンプ以降に作成または更新されたサーバーからすべてのレコードを取得します。次に、pushChangesはローカルで作成、更新、または削除されたすべてのレコードをサーバーに送信します。WatermelonDBは競合解決を自動的に処理します。
結論
React Native、Expo、WatermelonDBでオフラインファーストの睡眠トラッキングアプリケーションを正常に構築しました。このアーキテクチャは、ネットワーク状態に関係なくアプリが常に機能しレスポンシブであることを保証することで、優れたユーザー体験を提供します。
達成したこと:
- 、マネージドExpoプロジェクトでWatermelonDBをセットアップしました。
- 睡眠トラッキングデータのスキーマとモデルを定義しました。
- データベースの変更に自動的に更新されるリアクティブUIを構築しました。
- リモートサーバーとの堅牢な双方向同期を実装しました。
次のステップ:
- 同期プロセスにユーザー認証を追加する。
- 永続データベースを持つより精巧なバックエンドを実装する。
- リレーションなどのWatermelonDBのより高度な機能を探索する。
NetInfoなどのライブラリを使用して自動的に同期をトリガーする。
リソース
- WatermelonDBドキュメント: https://watermelondb.dev/
- WatermelonDB Expoプラグイン: https://github.com/morrowdigital/watermelondb-expo-plugin
- Expoドキュメント: https://docs.expo.dev/
クイックリファレンス
| 側面 | 主要情報 |
|---|---|
| トピック | 開発 |
| 関連性 | 患者教育と認識 |
| 行動 | 医療提供者との相談 |
| 頻度 | ケアチーム推奨通り |
| 証拠 | 現在の臨床ガイドラインに基づく |
よくある質問
Q: これが自分に該当するかどうかはどのようにわかりますか? A: 個人的な病歴と家族歴を医療提供者と相談し、個人のリスク因子を評価し、適切な次のステップを決定してください。
Q: 医師に何を聞くべきですか? A: 良い質問には次のようなものがあります:「私の結果はどういう意味ですか?」「私のおオプションは何ですか?」「どのような生活習慣の変更が助けますか?」「いつフォローアップすべきですか?」
Q: 生活習 습관の変更で助かることはありますか? A: ほとんどの健康状態は基盤となる対策から恩恵を受けます:バランスの取れた栄養、定期的な運動(週150分以上)、質の良い睡眠(7〜9時間)、ストレス管理、タバコの使用を避けること。
医療免責事項
この記事は教育目的のみを目的としており、医学的アドバイスを提供するものではありません。健康、治療、またはケアに関する決定する前に、常に資格のある医療提供者と相談してください。個人の状況は大きく異なり、ある人に適切なことが別の人に適切でない場合があります。
医療の緊急事態が発生した場合は、直ちに119番(または地域の緊急電話番号)に連絡してください。