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:

  1. User makes changes while offline
  2. Changes saved to local queue
  3. When back online, queue processes
  4. Successful sync removes from queue
  5. 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 items

Selective 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_id on all user-specific tables
  • created_at for time-based queries
  • entry_date for 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