Data Management
Local Storage
AsyncStorage
Used for persisting user preferences and temporary data.
Whatβs Stored:
- Theme preference (light/dark/system)
- Onboarding progress
- Recent searches
- Draft posts
- Offline queue
Example:
import AsyncStorage from '@react-native-async-storage/async-storage';
// Save data
await AsyncStorage.setItem('theme', 'dark');
// Retrieve data
const theme = await AsyncStorage.getItem('theme');
// Remove data
await AsyncStorage.removeItem('theme');Secure Storage
For sensitive data like tokens:
import * as SecureStore from 'expo-secure-store';
// Save securely
await SecureStore.setItemAsync('auth_token', token);
// Retrieve
const token = await SecureStore.getItemAsync('auth_token');
// Delete
await SecureStore.deleteItemAsync('auth_token');Image Cache
Photos cached locally for performance:
- Profile photos
- Food photos
- Workout photos
- Temporary uploads
Cache Management:
- Auto-cleanup after 7 days
- Manual clear in Settings
- Respects storage limits
Cloud Storage (Supabase)
Database Tables
users
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT UNIQUE NOT NULL,
full_name TEXT,
avatar_url TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);health_profiles
CREATE TABLE health_profiles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
height DECIMAL NOT NULL,
weight DECIMAL NOT NULL,
goal_weight DECIMAL,
age INTEGER NOT NULL,
gender TEXT NOT NULL,
activity_level TEXT NOT NULL,
dietary_preference TEXT,
daily_calorie_target INTEGER,
onboarding_complete BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);food_entries
CREATE TABLE food_entries (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
food_name TEXT NOT NULL,
calories INTEGER NOT NULL,
protein DECIMAL,
carbs DECIMAL,
fat DECIMAL,
fiber DECIMAL,
sugar DECIMAL,
serving_size TEXT,
meal_type TEXT CHECK (meal_type IN ('breakfast', 'lunch', 'dinner', 'snack')),
photo_url TEXT,
notes TEXT,
entry_date DATE NOT NULL DEFAULT CURRENT_DATE,
created_at TIMESTAMP DEFAULT NOW()
);workout_entries
CREATE TABLE workout_entries (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
exercise_name TEXT NOT NULL,
duration_minutes INTEGER NOT NULL,
calories_burned INTEGER NOT NULL,
exercise_type TEXT CHECK (exercise_type IN ('cardio', 'strength', 'flexibility', 'sports')),
intensity TEXT CHECK (intensity IN ('low', 'medium', 'high')),
photo_url TEXT,
notes TEXT,
entry_date DATE NOT NULL DEFAULT CURRENT_DATE,
created_at TIMESTAMP DEFAULT NOW()
);subscriptions
CREATE TABLE subscriptions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
status TEXT NOT NULL CHECK (status IN ('active', 'expired', 'canceled', 'free')),
plan_type TEXT CHECK (plan_type IN ('monthly', 'yearly')),
stripe_subscription_id TEXT,
stripe_customer_id TEXT,
current_period_start TIMESTAMP,
current_period_end TIMESTAMP,
ai_requests_used INTEGER DEFAULT 0,
ai_requests_limit INTEGER DEFAULT 50,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);analytics_cache
CREATE TABLE analytics_cache (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
cache_key TEXT NOT NULL,
data JSONB NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);Row-Level Security (RLS)
All tables have RLS policies to ensure data privacy:
Example Policy:
-- Users can only access their own food entries
CREATE POLICY "Users can view own food_entries"
ON food_entries
FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can insert own food_entries"
ON food_entries
FOR INSERT
WITH CHECK (auth.uid() = user_id);
CREATE POLICY "Users can update own food_entries"
ON food_entries
FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Users can delete own food_entries"
ON food_entries
FOR DELETE
USING (auth.uid() = user_id);File Storage
Photos stored in Supabase Storage:
Buckets:
avatarsβ Profile photos (public)food-photosβ Meal images (private)workout-photosβ Exercise images (private)
Upload Example:
const { data, error } = await supabase.storage
.from('food-photos')
.upload(`${userId}/${Date.now()}.jpg`, photo, {
contentType: 'image/jpeg',
upsert: false
});RLS Policies:
- Users can only access their own photos
- Public read for avatars
- Private for food/workout photos
Real-Time Sync
Subscription to Changes
Listen for real-time database updates:
const subscription = supabase
.from('food_entries')
.on('INSERT', (payload) => {
console.log('New entry!', payload.new);
addEntryToState(payload.new);
})
.on('UPDATE', (payload) => {
console.log('Entry updated!', payload.new);
updateEntryInState(payload.new);
})
.on('DELETE', (payload) => {
console.log('Entry deleted!', payload.old);
removeEntryFromState(payload.old.id);
})
.subscribe();
// Cleanup
return () => subscription.unsubscribe();Automatic Sync
- Changes saved locally first (optimistic updates)
- Synced to cloud in background
- Conflict resolution if offline changes clash
Offline Support
Offline Queue:
- User makes changes while offline
- Changes saved to local queue
- When back online, queue processes
- Successful sync removes from queue
- Conflicts resolved automatically
Data Aggregation
Daily Summaries
Pre-calculated daily stats for performance:
interface DailySummary {
user_id: string;
date: string;
total_calories_consumed: number;
total_calories_burned: number;
net_calories: number;
protein_total: number;
carbs_total: number;
fat_total: number;
workout_count: number;
workout_minutes: number;
}Analytics Cache
Expensive queries cached in analytics_cache table:
- Weekly/monthly aggregations
- Chart data
- Progress metrics
- Cached for 1 hour
Data Export
Export Formats
CSV Export:
- All food entries
- All workout entries
- Health metrics over time
- Daily summaries
PDF Reports:
- Weekly progress report
- Monthly summary
- Custom date ranges
- Visual charts included
JSON Export:
- Complete data dump
- Machine-readable
- API-compatible format
Export Process
// Request export
const { data } = await supabase.functions.invoke('export-data', {
body: {
format: 'csv',
startDate: '2024-01-01',
endDate: '2024-12-31'
}
});
// Download generated file
const url = data.download_url;Data Retention
Active Data
- Kept indefinitely while account is active
- Synced across devices
- Backed up daily
Deleted Data
- Soft delete for 30 days
- Can be restored during this period
- Permanently deleted after 30 days
Account Deletion
- All user data marked for deletion
- 30-day grace period
- Complete removal after 30 days
- Cannot be recovered
Performance Optimizations
Query Optimization
Pagination:
const { data } = await supabase
.from('food_entries')
.select('*')
.order('created_at', { ascending: false })
.range(0, 19); // First 20 itemsSelective Fields:
const { data } = await supabase
.from('food_entries')
.select('id, food_name, calories, created_at')
.eq('user_id', userId);Indexes
Optimized queries with database indexes:
user_idon all user-specific tablescreated_atfor time-based queriesentry_datefor daily aggregations- Composite indexes for common filters
Caching Strategy
Application-Level Cache:
- React Query for server state
- 5-minute stale time
- Background refetch
- Optimistic updates
Database-Level Cache:
- Materialized views for analytics
- Refreshed hourly
- Complex aggregations pre-computed