πŸ“š Mobile Health Apps

How to Implement Medication Reminder Apps with Smart Scheduling and Adherence Tracking

Complete technical guide to building medication reminder apps with React Native and Flutter, featuring intelligent scheduling algorithms, adherence analytics, and pharmacy integration.

✍️
Dr. Sarah Chen

Building Intelligent Medication Reminder Apps

Medication adherence is one of the most impactful use cases for mobile health apps, with proven ability to improve health outcomes and reduce hospitalizations by 41%. This comprehensive guide walks you through building a production-ready medication reminder app with smart scheduling, adherence tracking, and analytics.

Table of Contents

  1. Core Architecture
  2. Data Models
  3. Smart Scheduling Engine
  4. Push Notification Implementation
  5. Adherence Tracking
  6. Analytics Dashboard
  7. Pharmacy Integration
  8. Caregiver Coordination
  9. Gamification
  10. Voice Assistant Integration

Core Architecture

Technology Stack

Option 1: React Native

{
  "dependencies": {
    "@react-native-async-storage/async-storage": "^1.19.0",
    "@react-native-firebase/messaging": "^18.0.0",
    "@react-native-community/push-notification-ios": "^1.11.0",
    "react-native-push-notification": "^8.1.1",
    "react-native-background-fetch": "^4.1.0",
    "react-native-background-timer": "^2.4.1",
    "react-native-calendars": "^1.1300.0",
    "react-native-chart-kit": "^6.12.0",
    "date-fns": "^2.30.0",
    "uuid": "^9.0.0"
  }
}

Option 2: Flutter

dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.2.8
  flutter_local_notifications: ^14.1.0
  firebase_messaging: ^14.6.1
  timezone: ^0.9.2
  shared_preferences: ^2.2.0
  charts_flutter: ^0.12.0
  uuid: ^3.0.7

Project Structure

medication-reminder-app/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   β”œβ”€β”€ Medication.ts
β”‚   β”‚   β”œβ”€β”€ Schedule.ts
β”‚   β”‚   β”œβ”€β”€ Dose.ts
β”‚   β”‚   └── AdherenceLog.ts
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ scheduling/
β”‚   β”‚   β”‚   β”œβ”€β”€ SmartScheduler.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ RecurrenceEngine.ts
β”‚   β”‚   β”‚   └── ConflictResolver.ts
β”‚   β”‚   β”œβ”€β”€ notifications/
β”‚   β”‚   β”‚   β”œβ”€β”€ NotificationManager.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ LocalNotifications.ts
β”‚   β”‚   β”‚   └── PushNotifications.ts
β”‚   β”‚   β”œβ”€β”€ tracking/
β”‚   β”‚   β”‚   β”œβ”€β”€ AdherenceTracker.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ StreakCalculator.ts
β”‚   β”‚   β”‚   └── StatisticsEngine.ts
β”‚   β”‚   β”œβ”€β”€ pharmacy/
β”‚   β”‚   β”‚   β”œβ”€β”€ RefillManager.ts
β”‚   β”‚   β”‚   └── PharmacyAPI.ts
β”‚   β”‚   └── storage/
β”‚   β”‚       β”œβ”€β”€ MedicationStore.ts
β”‚   β”‚       └── SyncEngine.ts
β”‚   β”œβ”€β”€ screens/
β”‚   β”‚   β”œβ”€β”€ MedicationList/
β”‚   β”‚   β”œβ”€β”€ AddMedication/
β”‚   β”‚   β”œβ”€β”€ TodaySchedule/
β”‚   β”‚   β”œβ”€β”€ AdherenceReport/
β”‚   β”‚   └── Settings/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ MedicationCard/
β”‚   β”‚   β”œβ”€β”€ DoseButton/
β”‚   β”‚   β”œβ”€β”€ SchedulePicker/
β”‚   β”‚   β”œβ”€β”€ AdherenceChart/
β”‚   β”‚   └── StreakDisplay/
β”‚   └── utils/
β”‚       β”œβ”€β”€ dateUtils.ts
β”‚       β”œβ”€β”€ notificationUtils.ts
β”‚       └── analyticsUtils.ts

Data Models

Medication Model

// src/models/Medication.ts
export interface Medication {
  id: string;
  name: string;
  genericName?: string;
  dosage: string;
  form: MedicationForm;
  instructions: string;
  prescribingDoctor?: string;
  pharmacy?: PharmacyInfo;

  // Scheduling
  schedule: MedicationSchedule;

  // Refill information
  refillInfo?: RefillInfo;

  // Visual identification
  appearance?: MedicationAppearance;

  // Tracking
  startDate: Date;
  endDate?: Date;
  isActive: boolean;

  // Metadata
  createdAt: Date;
  updatedAt: Date;

  // Settings
  settings: MedicationSettings;
}

export type MedicationForm =
  | 'tablet'
  | 'capsule'
  | 'liquid'
  | 'injection'
  | 'inhaler'
  | 'patch'
  | 'drops'
  | 'cream'
  | 'other';

export interface MedicationSchedule {
  frequency: ScheduleFrequency;
  times: string[]; // ["08:00", "14:00", "20:00"]

  // For "as needed" medications
  asNeeded?: {
    maxDosesPerDay: number;
    minHoursBetweenDoses: number;
  };

  // For complex schedules
  customSchedule?: CustomSchedule;

  // Meal timing
  mealTiming?: 'before' | 'with' | 'after' | 'anytime';
  mealTimingMinutes?: number; // 30 minutes before meal
}

export type ScheduleFrequency =
  | 'once-daily'
  | 'twice-daily'
  | 'three-times-daily'
  | 'four-times-daily'
  | 'every-n-hours'
  | 'specific-days'
  | 'as-needed'
  | 'custom';

export interface CustomSchedule {
  pattern: 'weekly' | 'monthly' | 'alternating-days';
  daysOfWeek?: number[]; // [1, 3, 5] = Mon, Wed, Fri
  datesOfMonth?: number[]; // [1, 15] = 1st and 15th
  alternatingDays?: {
    onDays: number;
    offDays: number;
    startDate: Date;
  };
}

export interface RefillInfo {
  quantityRemaining: number;
  totalQuantity: number;
  refillsRemaining: number;
  lastRefillDate?: Date;
  nextRefillDate?: Date;
  autoRefill: boolean;
  reminderThreshold: number; // Days before running out
}

export interface MedicationAppearance {
  color: string;
  shape: string;
  imprint?: string;
  photoUri?: string;
}

export interface MedicationSettings {
  reminderEnabled: boolean;
  reminderAdvanceMinutes: number; // 15 minutes before scheduled time
  soundEnabled: boolean;
  vibrationEnabled: boolean;
  snoozeEnabled: boolean;
  snoozeDurationMinutes: number;
  requireConfirmation: boolean;
  photoConfirmation?: boolean; // Take photo of medication
  skipWeekends?: boolean;
}

export interface PharmacyInfo {
  name: string;
  phone: string;
  address: string;
  rxNumber?: string;
}

Adherence Log Model

// src/models/AdherenceLog.ts
export interface AdherenceLog {
  id: string;
  medicationId: string;

  // Scheduled information
  scheduledTime: Date;

  // Actual information
  takenTime?: Date;
  status: AdherenceStatus;

  // Confirmation
  confirmationMethod: ConfirmationMethod;
  photoUri?: string;
  location?: Coordinates;

  // Notes
  skippedReason?: SkippedReason;
  notes?: string;

  // Metadata
  createdAt: Date;
  syncedToServer: boolean;
}

export type AdherenceStatus =
  | 'taken'
  | 'missed'
  | 'skipped'
  | 'snoozed'
  | 'pending';

export type ConfirmationMethod =
  | 'manual'
  | 'photo'
  | 'nfc-bottle'
  | 'voice'
  | 'caregiver';

export type SkippedReason =
  | 'side-effects'
  | 'feeling-better'
  | 'forgot-to-bring'
  | 'ran-out'
  | 'other';

interface Coordinates {
  latitude: number;
  longitude: number;
}

Smart Scheduling Engine

Intelligent Scheduler

// src/services/scheduling/SmartScheduler.ts
import { Medication, MedicationSchedule, AdherenceLog } from '../../models';
import { addDays, addHours, setHours, setMinutes, startOfDay } from 'date-fns';

export interface ScheduledDose {
  id: string;
  medicationId: string;
  scheduledTime: Date;
  medication: Medication;
  taken: boolean;
  missed: boolean;
}

export class SmartScheduler {
  /**
   * Generate scheduled doses for date range
   */
  static generateSchedule(
    medications: Medication[],
    startDate: Date,
    endDate: Date
  ): ScheduledDose[] {
    const doses: ScheduledDose[] = [];

    for (const medication of medications) {
      if (!medication.isActive) continue;

      const medicationDoses = this.generateMedicationSchedule(
        medication,
        startDate,
        endDate
      );

      doses.push(...medicationDoses);
    }

    // Sort by scheduled time
    return doses.sort((a, b) =>
      a.scheduledTime.getTime() - b.scheduledTime.getTime()
    );
  }

  /**
   * Generate schedule for single medication
   */
  private static generateMedicationSchedule(
    medication: Medication,
    startDate: Date,
    endDate: Date
  ): ScheduledDose[] {
    const doses: ScheduledDose[] = [];
    const { schedule } = medication;

    // Don't schedule before medication start date
    const effectiveStartDate = medication.startDate > startDate
      ? medication.startDate
      : startDate;

    // Don't schedule after medication end date
    const effectiveEndDate = medication.endDate && medication.endDate < endDate
      ? medication.endDate
      : endDate;

    switch (schedule.frequency) {
      case 'once-daily':
      case 'twice-daily':
      case 'three-times-daily':
      case 'four-times-daily':
        doses.push(...this.generateDailySchedule(
          medication,
          effectiveStartDate,
          effectiveEndDate
        ));
        break;

      case 'every-n-hours':
        doses.push(...this.generateHourlySchedule(
          medication,
          effectiveStartDate,
          effectiveEndDate
        ));
        break;

      case 'specific-days':
        doses.push(...this.generateSpecificDaysSchedule(
          medication,
          effectiveStartDate,
          effectiveEndDate
        ));
        break;

      case 'custom':
        doses.push(...this.generateCustomSchedule(
          medication,
          effectiveStartDate,
          effectiveEndDate
        ));
        break;

      case 'as-needed':
        // As-needed medications don't have scheduled doses
        break;
    }

    return doses;
  }

  /**
   * Generate daily schedule (1-4 times per day)
   */
  private static generateDailySchedule(
    medication: Medication,
    startDate: Date,
    endDate: Date
  ): ScheduledDose[] {
    const doses: ScheduledDose[] = [];
    const { schedule, settings } = medication;

    let currentDate = startOfDay(startDate);

    while (currentDate <= endDate) {
      // Skip weekends if configured
      if (settings.skipWeekends && this.isWeekend(currentDate)) {
        currentDate = addDays(currentDate, 1);
        continue;
      }

      // Generate doses for this day
      for (const timeString of schedule.times) {
        const [hours, minutes] = timeString.split(':').map(Number);
        const scheduledTime = setMinutes(setHours(currentDate, hours), minutes);

        // Only include if within date range
        if (scheduledTime >= startDate && scheduledTime <= endDate) {
          doses.push({
            id: this.generateDoseId(medication.id, scheduledTime),
            medicationId: medication.id,
            scheduledTime,
            medication,
            taken: false,
            missed: false,
          });
        }
      }

      currentDate = addDays(currentDate, 1);
    }

    return doses;
  }

  /**
   * Generate hourly schedule (e.g., every 6 hours)
   */
  private static generateHourlySchedule(
    medication: Medication,
    startDate: Date,
    endDate: Date
  ): ScheduledDose[] {
    const doses: ScheduledDose[] = [];
    const { schedule } = medication;

    // Parse "every N hours" from schedule
    const hoursInterval = this.parseHoursInterval(schedule);
    if (!hoursInterval) return doses;

    let currentTime = startDate;

    while (currentTime <= endDate) {
      doses.push({
        id: this.generateDoseId(medication.id, currentTime),
        medicationId: medication.id,
        scheduledTime: currentTime,
        medication,
        taken: false,
        missed: false,
      });

      currentTime = addHours(currentTime, hoursInterval);
    }

    return doses;
  }

  /**
   * Generate specific days schedule (e.g., Mon, Wed, Fri)
   */
  private static generateSpecificDaysSchedule(
    medication: Medication,
    startDate: Date,
    endDate: Date
  ): ScheduledDose[] {
    const doses: ScheduledDose[] = [];
    const { schedule } = medication;

    if (!schedule.customSchedule?.daysOfWeek) return doses;

    let currentDate = startOfDay(startDate);

    while (currentDate <= endDate) {
      const dayOfWeek = currentDate.getDay();

      // Check if this day is in the schedule
      if (schedule.customSchedule.daysOfWeek.includes(dayOfWeek)) {
        // Generate doses for this day
        for (const timeString of schedule.times) {
          const [hours, minutes] = timeString.split(':').map(Number);
          const scheduledTime = setMinutes(setHours(currentDate, hours), minutes);

          if (scheduledTime >= startDate && scheduledTime <= endDate) {
            doses.push({
              id: this.generateDoseId(medication.id, scheduledTime),
              medicationId: medication.id,
              scheduledTime,
              medication,
              taken: false,
              missed: false,
            });
          }
        }
      }

      currentDate = addDays(currentDate, 1);
    }

    return doses;
  }

  /**
   * Generate custom schedule (alternating days, monthly, etc.)
   */
  private static generateCustomSchedule(
    medication: Medication,
    startDate: Date,
    endDate: Date
  ): ScheduledDose[] {
    const { schedule } = medication;

    if (!schedule.customSchedule) return [];

    switch (schedule.customSchedule.pattern) {
      case 'alternating-days':
        return this.generateAlternatingDaysSchedule(
          medication,
          startDate,
          endDate
        );

      case 'monthly':
        return this.generateMonthlySchedule(
          medication,
          startDate,
          endDate
        );

      default:
        return [];
    }
  }

  /**
   * Generate alternating days schedule (e.g., 2 days on, 2 days off)
   */
  private static generateAlternatingDaysSchedule(
    medication: Medication,
    startDate: Date,
    endDate: Date
  ): ScheduledDose[] {
    const doses: ScheduledDose[] = [];
    const { schedule } = medication;
    const alternating = schedule.customSchedule?.alternatingDays;

    if (!alternating) return doses;

    const cycleLength = alternating.onDays + alternating.offDays;
    const cycleStartDate = alternating.startDate;

    let currentDate = startOfDay(startDate);

    while (currentDate <= endDate) {
      // Calculate days since cycle start
      const daysSinceStart = Math.floor(
        (currentDate.getTime() - cycleStartDate.getTime()) / (1000 * 60 * 60 * 24)
      );

      const dayInCycle = daysSinceStart % cycleLength;

      // Check if this day is an "on" day
      if (dayInCycle < alternating.onDays) {
        // Generate doses for this day
        for (const timeString of schedule.times) {
          const [hours, minutes] = timeString.split(':').map(Number);
          const scheduledTime = setMinutes(setHours(currentDate, hours), minutes);

          if (scheduledTime >= startDate && scheduledTime <= endDate) {
            doses.push({
              id: this.generateDoseId(medication.id, scheduledTime),
              medicationId: medication.id,
              scheduledTime,
              medication,
              taken: false,
              missed: false,
            });
          }
        }
      }

      currentDate = addDays(currentDate, 1);
    }

    return doses;
  }

  /**
   * Optimize schedule to avoid conflicts
   */
  static optimizeSchedule(
    medications: Medication[],
    userPreferences: UserPreferences
  ): Medication[] {
    // Group medications by meal timing requirements
    const withMeals = medications.filter(m =>
      m.schedule.mealTiming && m.schedule.mealTiming !== 'anytime'
    );

    const anytime = medications.filter(m =>
      !m.schedule.mealTiming || m.schedule.mealTiming === 'anytime'
    );

    // Adjust meal-dependent medications to user's meal times
    const optimizedWithMeals = withMeals.map(med => {
      return this.adjustForMealTiming(med, userPreferences.mealTimes);
    });

    // Spread out "anytime" medications to avoid clustering
    const optimizedAnytime = this.spreadOutMedications(
      anytime,
      userPreferences.wakeTime,
      userPreferences.sleepTime
    );

    return [...optimizedWithMeals, ...optimizedAnytime];
  }

  /**
   * Adjust medication times based on meal schedule
   */
  private static adjustForMealTiming(
    medication: Medication,
    mealTimes: MealTimes
  ): Medication {
    const { schedule } = medication;
    const mealTiming = schedule.mealTiming;
    const offsetMinutes = schedule.mealTimingMinutes || 0;

    if (!mealTiming || mealTiming === 'anytime') {
      return medication;
    }

    const adjustedTimes: string[] = [];

    for (const time of schedule.times) {
      // Find closest meal time
      const mealTime = this.findClosestMealTime(time, mealTimes);

      // Adjust based on meal timing requirement
      let adjustedTime: Date;

      switch (mealTiming) {
        case 'before':
          adjustedTime = this.subtractMinutes(mealTime, offsetMinutes || 30);
          break;
        case 'with':
          adjustedTime = mealTime;
          break;
        case 'after':
          adjustedTime = this.addMinutes(mealTime, offsetMinutes || 30);
          break;
      }

      adjustedTimes.push(this.formatTime(adjustedTime));
    }

    return {
      ...medication,
      schedule: {
        ...schedule,
        times: adjustedTimes,
      },
    };
  }

  /**
   * Spread medications evenly throughout waking hours
   */
  private static spreadOutMedications(
    medications: Medication[],
    wakeTime: string,
    sleepTime: string
  ): Medication[] {
    // Sort medications by number of daily doses
    const sorted = [...medications].sort((a, b) =>
      b.schedule.times.length - a.schedule.times.length
    );

    const [wakeHour, wakeMinute] = wakeTime.split(':').map(Number);
    const [sleepHour, sleepMinute] = sleepTime.split(':').map(Number);

    const wakingMinutes = wakeHour * 60 + wakeMinute;
    const sleepingMinutes = sleepHour * 60 + sleepMinute;
    const totalWakingMinutes = sleepingMinutes - wakingMinutes;

    return sorted.map(medication => {
      const dosesPerDay = medication.schedule.times.length;

      // Calculate interval between doses
      const interval = totalWakingMinutes / (dosesPerDay + 1);

      // Generate evenly spaced times
      const times = Array.from({ length: dosesPerDay }, (_, i) => {
        const minutes = wakingMinutes + interval * (i + 1);
        const hours = Math.floor(minutes / 60);
        const mins = Math.round(minutes % 60);
        return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}`;
      });

      return {
        ...medication,
        schedule: {
          ...medication.schedule,
          times,
        },
      };
    });
  }

  // Utility methods
  private static generateDoseId(medicationId: string, time: Date): string {
    return `${medicationId}_${time.toISOString()}`;
  }

  private static isWeekend(date: Date): boolean {
    const day = date.getDay();
    return day === 0 || day === 6;
  }

  private static parseHoursInterval(schedule: MedicationSchedule): number | null {
    // This would parse schedule data to extract hours
    // Implementation depends on how you store the interval
    return null;
  }

  private static formatTime(date: Date): string {
    return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
  }
}

interface UserPreferences {
  wakeTime: string;
  sleepTime: string;
  mealTimes: MealTimes;
}

interface MealTimes {
  breakfast: string;
  lunch: string;
  dinner: string;
}

Push Notification Implementation

React Native Notification Manager

// src/services/notifications/NotificationManager.ts
import PushNotification from 'react-native-push-notification';
import { Platform } from 'react-native';
import { Medication, ScheduledDose } from '../../models';
import { addMinutes, subMinutes } from 'date-fns';

export class NotificationManager {
  /**
   * Initialize notification system
   */
  static initialize(): void {
    PushNotification.configure({
      onNotification: (notification) => {
        this.handleNotificationAction(notification);
      },

      permissions: {
        alert: true,
        badge: true,
        sound: true,
      },

      popInitialNotification: true,
      requestPermissions: Platform.OS === 'ios',
    });

    this.createNotificationChannels();
  }

  /**
   * Create notification channels (Android)
   */
  private static createNotificationChannels(): void {
    if (Platform.OS !== 'android') return;

    PushNotification.createChannel(
      {
        channelId: 'medication-reminders',
        channelName: 'Medication Reminders',
        channelDescription: 'Reminders to take your medications',
        importance: 4,
        vibrate: true,
        playSound: true,
        soundName: 'default',
      },
      (created) => console.log(`Notification channel created: ${created}`)
    );
  }

  /**
   * Schedule reminder for medication dose
   */
  static async scheduleReminder(dose: ScheduledDose): Promise<void> {
    const { medication, scheduledTime } = dose;
    const { settings } = medication;

    if (!settings.reminderEnabled) return;

    // Calculate reminder time (advance by configured minutes)
    const reminderTime = subMinutes(
      scheduledTime,
      settings.reminderAdvanceMinutes
    );

    // Don't schedule reminders in the past
    if (reminderTime < new Date()) return;

    PushNotification.localNotificationSchedule({
      id: this.getNotificationId(dose.id),
      channelId: 'medication-reminders',

      title: 'πŸ’Š Medication Reminder',
      message: `Time to take ${medication.name} (${medication.dosage})`,

      date: reminderTime,
      allowWhileIdle: true,

      playSound: settings.soundEnabled,
      soundName: settings.soundEnabled ? 'default' : undefined,
      vibrate: settings.vibrationEnabled,
      vibration: settings.vibrationEnabled ? 300 : undefined,

      // Action buttons
      actions: ['TAKE', 'SNOOZE', 'SKIP'],

      userInfo: {
        doseId: dose.id,
        medicationId: medication.id,
        scheduledTime: scheduledTime.toISOString(),
      },

      // Badge count
      number: 1,
    });

    console.log(`Scheduled reminder for ${medication.name} at ${reminderTime}`);
  }

  /**
   * Schedule all reminders for date range
   */
  static async scheduleAllReminders(
    doses: ScheduledDose[]
  ): Promise<void> {
    // Cancel existing notifications
    await this.cancelAllReminders();

    // Schedule new notifications
    for (const dose of doses) {
      if (!dose.taken && !dose.missed) {
        await this.scheduleReminder(dose);
      }
    }

    console.log(`Scheduled ${doses.length} medication reminders`);
  }

  /**
   * Cancel reminder for specific dose
   */
  static cancelReminder(doseId: string): void {
    const notificationId = this.getNotificationId(doseId);
    PushNotification.cancelLocalNotification(notificationId);
  }

  /**
   * Cancel all reminders
   */
  static async cancelAllReminders(): Promise<void> {
    PushNotification.cancelAllLocalNotifications();
  }

  /**
   * Handle notification action
   */
  private static async handleNotificationAction(
    notification: any
  ): Promise<void> {
    const { action, userInfo } = notification;

    if (!userInfo?.doseId) return;

    switch (action) {
      case 'TAKE':
        await this.markDoseTaken(userInfo.doseId);
        break;

      case 'SNOOZE':
        await this.snoozeDose(userInfo.doseId, 15); // 15 minutes
        break;

      case 'SKIP':
        await this.skipDose(userInfo.doseId);
        break;
    }
  }

  /**
   * Snooze dose reminder
   */
  private static async snoozeDose(
    doseId: string,
    minutes: number
  ): Promise<void> {
    // Get dose information
    const dose = await this.getDose(doseId);
    if (!dose) return;

    // Cancel current reminder
    this.cancelReminder(doseId);

    // Schedule new reminder for snooze time
    const snoozeTime = addMinutes(new Date(), minutes);

    PushNotification.localNotificationSchedule({
      id: this.getNotificationId(doseId),
      channelId: 'medication-reminders',

      title: 'πŸ’Š Medication Reminder (Snoozed)',
      message: `Don't forget: ${dose.medication.name}`,

      date: snoozeTime,
      allowWhileIdle: true,

      actions: ['TAKE', 'SNOOZE', 'SKIP'],

      userInfo: {
        doseId: dose.id,
        medicationId: dose.medication.id,
        scheduledTime: dose.scheduledTime.toISOString(),
      },
    });

    console.log(`Snoozed ${dose.medication.name} for ${minutes} minutes`);
  }

  /**
   * Send daily summary notification
   */
  static async sendDailySummary(
    doses: ScheduledDose[],
    time: Date
  ): Promise<void> {
    const pendingDoses = doses.filter(d => !d.taken && !d.missed);

    if (pendingDoses.length === 0) return;

    const medicationNames = pendingDoses
      .map(d => d.medication.name)
      .join(', ');

    PushNotification.localNotificationSchedule({
      channelId: 'medication-reminders',

      title: `πŸ“‹ ${pendingDoses.length} Medications Today`,
      message: `Don't forget: ${medicationNames}`,

      date: time,
      allowWhileIdle: true,

      userInfo: {
        type: 'daily-summary',
        doseCount: pendingDoses.length,
      },
    });
  }

  /**
   * Send refill reminder
   */
  static async sendRefillReminder(medication: Medication): Promise<void> {
    if (!medication.refillInfo) return;

    const { quantityRemaining, reminderThreshold } = medication.refillInfo;

    if (quantityRemaining <= reminderThreshold) {
      PushNotification.localNotification({
        channelId: 'medication-reminders',

        title: 'πŸ₯ Refill Reminder',
        message: `Only ${quantityRemaining} doses of ${medication.name} remaining. Time to refill!`,

        actions: ['ORDER REFILL', 'REMIND LATER'],

        userInfo: {
          type: 'refill-reminder',
          medicationId: medication.id,
        },
      });
    }
  }

  // Utility methods
  private static getNotificationId(doseId: string): string {
    // Generate numeric ID from string
    let hash = 0;
    for (let i = 0; i < doseId.length; i++) {
      hash = ((hash << 5) - hash) + doseId.charCodeAt(i);
      hash = hash & hash;
    }
    return Math.abs(hash).toString();
  }

  private static async getDose(doseId: string): Promise<ScheduledDose | null> {
    // Fetch from storage - implementation depends on your data layer
    return null;
  }

  private static async markDoseTaken(doseId: string): Promise<void> {
    // Update adherence log - implementation depends on your data layer
  }

  private static async skipDose(doseId: string): Promise<void> {
    // Update adherence log - implementation depends on your data layer
  }
}

Flutter Notification Manager

// lib/services/notifications/notification_manager.dart
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:timezone/timezone.dart' as tz;
import '../models/medication.dart';
import '../models/scheduled_dose.dart';

class NotificationManager {
  static final FlutterLocalNotificationsPlugin _notifications =
      FlutterLocalNotificationsPlugin();

  /// Initialize notification system
  static Future<void> initialize() async {
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('@mipmap/ic_launcher');

    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings(
      requestSoundPermission: true,
      requestBadgePermission: true,
      requestAlertPermission: true,
    );

    final InitializationSettings initializationSettings =
        InitializationSettings(
      android: initializationSettingsAndroid,
      iOS: initializationSettingsIOS,
    );

    await _notifications.initialize(
      initializationSettings,
      onSelectNotification: _onNotificationTapped,
    );

    // Create notification channels (Android)
    await _createNotificationChannels();
  }

  /// Create notification channels
  static Future<void> _createNotificationChannels() async {
    const AndroidNotificationChannel channel = AndroidNotificationChannel(
      'medication_reminders',
      'Medication Reminders',
      description: 'Reminders to take your medications',
      importance: Importance.high,
      playSound: true,
      enableVibration: true,
    );

    await _notifications
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);
  }

  /// Schedule reminder for medication dose
  static Future<void> scheduleReminder(ScheduledDose dose) async {
    final medication = dose.medication;
    final settings = medication.settings;

    if (!settings.reminderEnabled) return;

    // Calculate reminder time
    final reminderTime = dose.scheduledTime.subtract(
      Duration(minutes: settings.reminderAdvanceMinutes),
    );

    // Don't schedule reminders in the past
    if (reminderTime.isBefore(DateTime.now())) return;

    // Android notification details
    final AndroidNotificationDetails androidDetails =
        AndroidNotificationDetails(
      'medication_reminders',
      'Medication Reminders',
      channelDescription: 'Reminders to take your medications',
      importance: Importance.high,
      priority: Priority.high,
      playSound: settings.soundEnabled,
      enableVibration: settings.vibrationEnabled,
      actions: <AndroidNotificationAction>[
        AndroidNotificationAction('take', 'Take'),
        AndroidNotificationAction('snooze', 'Snooze'),
        AndroidNotificationAction('skip', 'Skip'),
      ],
    );

    // iOS notification details
    final IOSNotificationDetails iosDetails = IOSNotificationDetails(
      sound: settings.soundEnabled ? 'default' : null,
    );

    final NotificationDetails details = NotificationDetails(
      android: androidDetails,
      iOS: iosDetails,
    );

    await _notifications.zonedSchedule(
      _getNotificationId(dose.id),
      'πŸ’Š Medication Reminder',
      'Time to take ${medication.name} (${medication.dosage})',
      tz.TZDateTime.from(reminderTime, tz.local),
      details,
      androidAllowWhileIdle: true,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      payload: dose.id,
    );

    print('Scheduled reminder for ${medication.name} at $reminderTime');
  }

  /// Schedule all reminders
  static Future<void> scheduleAllReminders(
      List<ScheduledDose> doses) async {
    await cancelAllReminders();

    for (final dose in doses) {
      if (!dose.taken && !dose.missed) {
        await scheduleReminder(dose);
      }
    }

    print('Scheduled ${doses.length} medication reminders');
  }

  /// Cancel reminder
  static Future<void> cancelReminder(String doseId) async {
    await _notifications.cancel(_getNotificationId(doseId));
  }

  /// Cancel all reminders
  static Future<void> cancelAllReminders() async {
    await _notifications.cancelAll();
  }

  /// Handle notification tap
  static Future<void> _onNotificationTapped(String? payload) async {
    if (payload == null) return;

    // Navigate to medication detail screen
    // Implementation depends on your navigation setup
  }

  /// Generate notification ID from dose ID
  static int _getNotificationId(String doseId) {
    return doseId.hashCode;
  }

  /// Send daily summary notification
  static Future<void> sendDailySummary(
    List<ScheduledDose> doses,
    DateTime time,
  ) async {
    final pendingDoses = doses.where((d) => !d.taken && !d.missed).toList();

    if (pendingDoses.isEmpty) return;

    final medicationNames =
        pendingDoses.map((d) => d.medication.name).join(', ');

    const AndroidNotificationDetails androidDetails =
        AndroidNotificationDetails(
      'medication_reminders',
      'Medication Reminders',
      importance: Importance.high,
      priority: Priority.high,
    );

    const IOSNotificationDetails iosDetails = IOSNotificationDetails();

    const NotificationDetails details = NotificationDetails(
      android: androidDetails,
      iOS: iosDetails,
    );

    await _notifications.zonedSchedule(
      999999, // Unique ID for daily summary
      'πŸ“‹ ${pendingDoses.length} Medications Today',
      'Don\'t forget: $medicationNames',
      tz.TZDateTime.from(time, tz.local),
      details,
      androidAllowWhileIdle: true,
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
    );
  }
}

Adherence Tracking

Adherence Tracker Service

// src/services/tracking/AdherenceTracker.ts
import { AdherenceLog, AdherenceStatus, Medication } from '../../models';
import { SecureStorage } from '../storage/SecureStorage';
import { differenceInMinutes, startOfDay, endOfDay } from 'date-fns';

export interface AdherenceMetrics {
  totalDoses: number;
  takenDoses: number;
  missedDoses: number;
  skippedDoses: number;
  adherenceRate: number;
  currentStreak: number;
  longestStreak: number;
  averageDelay: number; // Minutes late on average
}

export class AdherenceTracker {
  private static readonly STORAGE_KEY = 'adherence_logs';
  private static readonly ON_TIME_THRESHOLD_MINUTES = 30;

  /**
   * Log medication taken
   */
  static async logDoseTaken(
    medicationId: string,
    scheduledTime: Date,
    takenTime: Date = new Date(),
    confirmationMethod: 'manual' | 'photo' | 'nfc-bottle' | 'voice' = 'manual',
    photoUri?: string
  ): Promise<AdherenceLog> {
    const log: AdherenceLog = {
      id: this.generateLogId(),
      medicationId,
      scheduledTime,
      takenTime,
      status: 'taken',
      confirmationMethod,
      photoUri,
      createdAt: new Date(),
      syncedToServer: false,
    };

    await this.saveLog(log);

    // Cancel reminder for this dose
    await NotificationManager.cancelReminder(log.id);

    return log;
  }

  /**
   * Log missed dose
   */
  static async logMissedDose(
    medicationId: string,
    scheduledTime: Date
  ): Promise<AdherenceLog> {
    const log: AdherenceLog = {
      id: this.generateLogId(),
      medicationId,
      scheduledTime,
      status: 'missed',
      confirmationMethod: 'manual',
      createdAt: new Date(),
      syncedToServer: false,
    };

    await this.saveLog(log);

    return log;
  }

  /**
   * Log skipped dose
   */
  static async logSkippedDose(
    medicationId: string,
    scheduledTime: Date,
    reason: SkippedReason,
    notes?: string
  ): Promise<AdherenceLog> {
    const log: AdherenceLog = {
      id: this.generateLogId(),
      medicationId,
      scheduledTime,
      status: 'skipped',
      confirmationMethod: 'manual',
      skippedReason: reason,
      notes,
      createdAt: new Date(),
      syncedToServer: false,
    };

    await this.saveLog(log);

    // Cancel reminder for this dose
    await NotificationManager.cancelReminder(log.id);

    return log;
  }

  /**
   * Get adherence logs for medication
   */
  static async getLogs(
    medicationId: string,
    startDate?: Date,
    endDate?: Date
  ): Promise<AdherenceLog[]> {
    const allLogs = await this.getAllLogs();

    let filtered = allLogs.filter(log => log.medicationId === medicationId);

    if (startDate) {
      filtered = filtered.filter(log => log.scheduledTime >= startDate);
    }

    if (endDate) {
      filtered = filtered.filter(log => log.scheduledTime <= endDate);
    }

    return filtered.sort((a, b) =>
      b.scheduledTime.getTime() - a.scheduledTime.getTime()
    );
  }

  /**
   * Calculate adherence metrics
   */
  static async calculateMetrics(
    medicationId: string,
    startDate: Date,
    endDate: Date
  ): Promise<AdherenceMetrics> {
    const logs = await this.getLogs(medicationId, startDate, endDate);

    const totalDoses = logs.length;
    const takenDoses = logs.filter(l => l.status === 'taken').length;
    const missedDoses = logs.filter(l => l.status === 'missed').length;
    const skippedDoses = logs.filter(l => l.status === 'skipped').length;

    const adherenceRate = totalDoses > 0
      ? (takenDoses / totalDoses) * 100
      : 0;

    const currentStreak = this.calculateCurrentStreak(logs);
    const longestStreak = this.calculateLongestStreak(logs);
    const averageDelay = this.calculateAverageDelay(logs);

    return {
      totalDoses,
      takenDoses,
      missedDoses,
      skippedDoses,
      adherenceRate: Math.round(adherenceRate * 10) / 10,
      currentStreak,
      longestStreak,
      averageDelay,
    };
  }

  /**
   * Calculate current streak (consecutive days taken)
   */
  private static calculateCurrentStreak(logs: AdherenceLog[]): number {
    const sortedLogs = [...logs].sort((a, b) =>
      b.scheduledTime.getTime() - a.scheduledTime.getTime()
    );

    let streak = 0;
    let currentDate = startOfDay(new Date());

    for (const log of sortedLogs) {
      const logDate = startOfDay(log.scheduledTime);

      if (log.status === 'taken') {
        if (logDate.getTime() === currentDate.getTime()) {
          streak++;
          currentDate = new Date(currentDate.getTime() - 24 * 60 * 60 * 1000);
        } else if (logDate < currentDate) {
          break;
        }
      }
    }

    return streak;
  }

  /**
   * Calculate longest streak ever
   */
  private static calculateLongestStreak(logs: AdherenceLog[]): number {
    const sortedLogs = [...logs].sort((a, b) =>
      a.scheduledTime.getTime() - b.scheduledTime.getTime()
    );

    let longestStreak = 0;
    let currentStreak = 0;
    let lastDate: Date | null = null;

    for (const log of sortedLogs) {
      const logDate = startOfDay(log.scheduledTime);

      if (log.status === 'taken') {
        if (!lastDate || this.isConsecutiveDay(lastDate, logDate)) {
          currentStreak++;
          longestStreak = Math.max(longestStreak, currentStreak);
        } else {
          currentStreak = 1;
        }

        lastDate = logDate;
      } else {
        currentStreak = 0;
      }
    }

    return longestStreak;
  }

  /**
   * Calculate average delay (how late doses are taken)
   */
  private static calculateAverageDelay(logs: AdherenceLog[]): number {
    const takenLogs = logs.filter(l => l.status === 'taken' && l.takenTime);

    if (takenLogs.length === 0) return 0;

    const totalDelay = takenLogs.reduce((sum, log) => {
      const delay = differenceInMinutes(log.takenTime!, log.scheduledTime);
      return sum + Math.max(0, delay); // Only count positive delays
    }, 0);

    return Math.round(totalDelay / takenLogs.length);
  }

  /**
   * Check if two dates are consecutive days
   */
  private static isConsecutiveDay(date1: Date, date2: Date): boolean {
    const diff = Math.abs(date2.getTime() - date1.getTime());
    const dayInMs = 24 * 60 * 60 * 1000;
    return diff === dayInMs;
  }

  /**
   * Get adherence insights
   */
  static async getInsights(
    medicationId: string
  ): Promise<AdherenceInsight[]> {
    const logs = await this.getLogs(medicationId);
    const insights: AdherenceInsight[] = [];

    // Analyze missed doses by time of day
    const missedByTime = this.analyzeByTimeOfDay(logs, 'missed');
    if (Object.keys(missedByTime).length > 0) {
      const mostMissedTime = Object.entries(missedByTime)
        .sort((a, b) => b[1] - a[1])[0][0];

      insights.push({
        type: 'missed-time-pattern',
        title: 'Missed Dose Pattern',
        description: `You often miss doses around ${mostMissedTime}. Consider adjusting your schedule.`,
        actionable: true,
        action: 'adjust-schedule',
      });
    }

    // Analyze adherence trends
    const recentLogs = logs.slice(0, 14); // Last 14 doses
    const recentAdherence = (recentLogs.filter(l => l.status === 'taken').length / recentLogs.length) * 100;

    const allTimeAdherence = (logs.filter(l => l.status === 'taken').length / logs.length) * 100;

    if (recentAdherence < allTimeAdherence - 10) {
      insights.push({
        type: 'declining-adherence',
        title: 'Adherence Declining',
        description: `Your recent adherence (${recentAdherence.toFixed(0)}%) is lower than your average (${allTimeAdherence.toFixed(0)}%).`,
        actionable: true,
        action: 'increase-reminders',
      });
    }

    return insights;
  }

  /**
   * Analyze logs by time of day
   */
  private static analyzeByTimeOfDay(
    logs: AdherenceLog[],
    status: AdherenceStatus
  ): Record<string, number> {
    const byTime: Record<string, number> = {};

    const filtered = logs.filter(l => l.status === status);

    for (const log of filtered) {
      const hour = log.scheduledTime.getHours();
      const timeSlot = this.getTimeSlot(hour);

      byTime[timeSlot] = (byTime[timeSlot] || 0) + 1;
    }

    return byTime;
  }

  /**
   * Get time slot label
   */
  private static getTimeSlot(hour: number): string {
    if (hour < 6) return 'Night (12am-6am)';
    if (hour < 12) return 'Morning (6am-12pm)';
    if (hour < 18) return 'Afternoon (12pm-6pm)';
    return 'Evening (6pm-12am)';
  }

  // Storage methods
  private static async saveLog(log: AdherenceLog): Promise<void> {
    const logs = await this.getAllLogs();
    logs.push(log);
    await SecureStorage.setItem(this.STORAGE_KEY, logs);
  }

  private static async getAllLogs(): Promise<AdherenceLog[]> {
    const logs = await SecureStorage.getItem<AdherenceLog[]>(this.STORAGE_KEY);
    return logs || [];
  }

  private static generateLogId(): string {
    return `log_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
}

interface AdherenceInsight {
  type: string;
  title: string;
  description: string;
  actionable: boolean;
  action?: string;
}

Analytics Dashboard

Adherence Chart Component

// src/components/AdherenceChart.tsx
import React from 'react';
import { View, Dimensions } from 'react-native';
import { LineChart, BarChart } from 'react-native-chart-kit';
import { AdherenceLog } from '../models';
import { startOfDay, format, subDays } from 'date-fns';

interface AdherenceChartProps {
  logs: AdherenceLog[];
  days: number; // Number of days to show
}

export const AdherenceChart: React.FC<AdherenceChartProps> = ({ logs, days }) => {
  const chartData = React.useMemo(() => {
    const today = startOfDay(new Date());
    const dates: string[] = [];
    const adherenceRates: number[] = [];

    // Generate last N days
    for (let i = days - 1; i >= 0; i--) {
      const date = subDays(today, i);
      dates.push(format(date, 'MMM d'));

      // Calculate adherence rate for this day
      const dayStart = startOfDay(date);
      const dayEnd = new Date(dayStart);
      dayEnd.setHours(23, 59, 59, 999);

      const dayLogs = logs.filter(
        log =>
          log.scheduledTime >= dayStart && log.scheduledTime <= dayEnd
      );

      const takenCount = dayLogs.filter(l => l.status === 'taken').length;
      const adherenceRate =
        dayLogs.length > 0 ? (takenCount / dayLogs.length) * 100 : 0;

      adherenceRates.push(adherenceRate);
    }

    return {
      labels: dates,
      datasets: [
        {
          data: adherenceRates,
          color: (opacity = 1) => `rgba(52, 211, 153, ${opacity})`,
          strokeWidth: 2,
        },
      ],
    };
  }, [logs, days]);

  return (
    <View>
      <LineChart
        data={chartData}
        width={Dimensions.get('window').width - 32}
        height={220}
        chartConfig={{
          backgroundColor: '#ffffff',
          backgroundGradientFrom: '#ffffff',
          backgroundGradientTo: '#ffffff',
          decimalPlaces: 0,
          color: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
          labelColor: (opacity = 1) => `rgba(0, 0, 0, ${opacity})`,
          style: {
            borderRadius: 16,
          },
          propsForDots: {
            r: '4',
            strokeWidth: '2',
            stroke: '#34d399',
          },
        }}
        bezier
        style={{
          marginVertical: 8,
          borderRadius: 16,
        }}
        formatYLabel={(value) => `${value}%`}
      />
    </View>
  );
};

Pharmacy Integration

Refill Manager

// src/services/pharmacy/RefillManager.ts
import { Medication, RefillInfo } from '../../models';
import { addDays } from 'date-fns';

export class RefillManager {
  /**
   * Calculate days until refill needed
   */
  static calculateDaysUntilRefill(medication: Medication): number {
    if (!medication.refillInfo) return Infinity;

    const { quantityRemaining } = medication.refillInfo;
    const dailyDosesCount = medication.schedule.times.length;

    return Math.floor(quantityRemaining / dailyDosesCount);
  }

  /**
   * Check if refill is needed
   */
  static needsRefill(medication: Medication): boolean {
    if (!medication.refillInfo) return false;

    const daysUntilRefill = this.calculateDaysUntilRefill(medication);
    const threshold = medication.refillInfo.reminderThreshold || 7;

    return daysUntilRefill <= threshold;
  }

  /**
   * Update quantity after dose taken
   */
  static async updateQuantityAfterDose(
    medication: Medication
  ): Promise<Medication> {
    if (!medication.refillInfo) return medication;

    const updatedRefillInfo: RefillInfo = {
      ...medication.refillInfo,
      quantityRemaining: Math.max(0, medication.refillInfo.quantityRemaining - 1),
    };

    // Calculate next refill date
    const daysUntilRefill = Math.floor(
      updatedRefillInfo.quantityRemaining /
        medication.schedule.times.length
    );
    updatedRefillInfo.nextRefillDate = addDays(new Date(), daysUntilRefill);

    const updatedMedication = {
      ...medication,
      refillInfo: updatedRefillInfo,
    };

    // Check if refill reminder should be sent
    if (this.needsRefill(updatedMedication)) {
      await NotificationManager.sendRefillReminder(updatedMedication);
    }

    return updatedMedication;
  }

  /**
   * Process refill
   */
  static async processRefill(
    medication: Medication,
    quantity: number
  ): Promise<Medication> {
    if (!medication.refillInfo) return medication;

    const updatedRefillInfo: RefillInfo = {
      ...medication.refillInfo,
      quantityRemaining: quantity,
      totalQuantity: quantity,
      refillsRemaining: Math.max(0, medication.refillInfo.refillsRemaining - 1),
      lastRefillDate: new Date(),
    };

    // Calculate next refill date
    const daysUntilNextRefill = Math.floor(
      quantity / medication.schedule.times.length
    );
    updatedRefillInfo.nextRefillDate = addDays(new Date(), daysUntilNextRefill);

    return {
      ...medication,
      refillInfo: updatedRefillInfo,
    };
  }

  /**
   * Order refill via pharmacy API
   */
  static async orderRefill(medication: Medication): Promise<{
    success: boolean;
    orderNumber?: string;
    error?: string;
  }> {
    if (!medication.pharmacy?.rxNumber) {
      return {
        success: false,
        error: 'No pharmacy or prescription number on file',
      };
    }

    try {
      const response = await fetch('https://api.pharmacy.com/refills', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${await getAuthToken()}`,
        },
        body: JSON.stringify({
          rxNumber: medication.pharmacy.rxNumber,
          pharmacyName: medication.pharmacy.name,
          medicationName: medication.name,
        }),
      });

      if (response.ok) {
        const data = await response.json();
        return {
          success: true,
          orderNumber: data.orderNumber,
        };
      } else {
        return {
          success: false,
          error: 'Failed to submit refill order',
        };
      }
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }
}

Caregiver Coordination

Caregiver Alerts

// src/services/caregivers/CaregiverManager.ts
export interface Caregiver {
  id: string;
  name: string;
  relationship: string;
  email: string;
  phone: string;
  notificationPreferences: {
    missedDoses: boolean;
    lowAdherence: boolean;
    refillNeeded: boolean;
    emergencyOnly: boolean;
  };
}

export class CaregiverManager {
  /**
   * Send missed dose alert to caregivers
   */
  static async alertMissedDose(
    medication: Medication,
    scheduledTime: Date,
    caregivers: Caregiver[]
  ): Promise<void> {
    const eligibleCaregivers = caregivers.filter(
      c => c.notificationPreferences.missedDoses
    );

    for (const caregiver of eligibleCaregivers) {
      await this.sendAlert(caregiver, {
        type: 'missed-dose',
        title: 'Missed Medication',
        message: `Missed dose of ${medication.name} scheduled for ${format(
          scheduledTime,
          'h:mm a'
        )}`,
        severity: 'medium',
      });
    }
  }

  /**
   * Send low adherence alert
   */
  static async alertLowAdherence(
    medicationName: string,
    adherenceRate: number,
    caregivers: Caregiver[]
  ): Promise<void> {
    const eligibleCaregivers = caregivers.filter(
      c => c.notificationPreferences.lowAdherence
    );

    for (const caregiver of eligibleCaregivers) {
      await this.sendAlert(caregiver, {
        type: 'low-adherence',
        title: 'Low Medication Adherence',
        message: `${medicationName} adherence has dropped to ${adherenceRate.toFixed(
          0
        )}%`,
        severity: 'high',
      });
    }
  }

  /**
   * Send alert to caregiver
   */
  private static async sendAlert(
    caregiver: Caregiver,
    alert: Alert
  ): Promise<void> {
    // Send push notification if app installed
    await this.sendPushNotification(caregiver, alert);

    // Send SMS for high/emergency severity
    if (alert.severity === 'high' || alert.severity === 'emergency') {
      await this.sendSMS(caregiver.phone, alert.message);
    }

    // Send email
    await this.sendEmail(caregiver.email, alert);
  }

  private static async sendPushNotification(
    caregiver: Caregiver,
    alert: Alert
  ): Promise<void> {
    // Implementation depends on backend push notification service
  }

  private static async sendSMS(phone: string, message: string): Promise<void> {
    // Implementation depends on SMS service (Twilio, etc.)
  }

  private static async sendEmail(
    email: string,
    alert: Alert
  ): Promise<void> {
    // Implementation depends on email service
  }
}

interface Alert {
  type: string;
  title: string;
  message: string;
  severity: 'low' | 'medium' | 'high' | 'emergency';
}

Gamification

Achievement System

// src/services/gamification/AchievementEngine.ts
export interface Achievement {
  id: string;
  title: string;
  description: string;
  icon: string;
  category: 'streak' | 'adherence' | 'milestone';
  requirement: number;
  progress: number;
  unlocked: boolean;
  unlockedAt?: Date;
}

export class AchievementEngine {
  static readonly ACHIEVEMENTS: Omit<Achievement, 'progress' | 'unlocked' | 'unlockedAt'>[] = [
    {
      id: 'first-week',
      title: 'Week Warrior',
      description: '7 days of perfect adherence',
      icon: '🎯',
      category: 'streak',
      requirement: 7,
    },
    {
      id: 'month-master',
      title: 'Month Master',
      description: '30 days of perfect adherence',
      icon: 'πŸ†',
      category: 'streak',
      requirement: 30,
    },
    {
      id: 'hundred-days',
      title: 'Century Club',
      description: '100 days of perfect adherence',
      icon: 'πŸ’―',
      category: 'streak',
      requirement: 100,
    },
    {
      id: 'ninety-percent',
      title: '90% Club',
      description: 'Maintain 90% adherence for 30 days',
      icon: '⭐',
      category: 'adherence',
      requirement: 90,
    },
    {
      id: 'hundred-doses',
      title: 'Dedication Award',
      description: 'Take 100 doses on time',
      icon: 'πŸŽ–οΈ',
      category: 'milestone',
      requirement: 100,
    },
  ];

  /**
   * Check and unlock achievements
   */
  static async checkAchievements(
    logs: AdherenceLog[],
    currentAchievements: Achievement[]
  ): Promise<Achievement[]> {
    const newAchievements: Achievement[] = [];

    for (const template of this.ACHIEVEMENTS) {
      const existing = currentAchievements.find(a => a.id === template.id);

      if (existing?.unlocked) continue;

      const progress = this.calculateProgress(template, logs);

      if (progress >= template.requirement && !existing?.unlocked) {
        // Achievement unlocked!
        newAchievements.push({
          ...template,
          progress,
          unlocked: true,
          unlockedAt: new Date(),
        });
      }
    }

    return newAchievements;
  }

  /**
   * Calculate progress towards achievement
   */
  private static calculateProgress(
    achievement: Omit<Achievement, 'progress' | 'unlocked' | 'unlockedAt'>,
    logs: AdherenceLog[]
  ): number {
    switch (achievement.category) {
      case 'streak':
        return this.calculateStreak(logs);

      case 'adherence':
        return this.calculate30DayAdherence(logs);

      case 'milestone':
        return this.countOnTimeDoses(logs);

      default:
        return 0;
    }
  }

  private static calculateStreak(logs: AdherenceLog[]): number {
    // Use same logic as AdherenceTracker.calculateCurrentStreak
    return 0;
  }

  private static calculate30DayAdherence(logs: AdherenceLog[]): number {
    const thirtyDaysAgo = subDays(new Date(), 30);
    const recentLogs = logs.filter(l => l.scheduledTime >= thirtyDaysAgo);

    if (recentLogs.length === 0) return 0;

    const takenCount = recentLogs.filter(l => l.status === 'taken').length;
    return (takenCount / recentLogs.length) * 100;
  }

  private static countOnTimeDoses(logs: AdherenceLog[]): number {
    return logs.filter(l => {
      if (l.status !== 'taken' || !l.takenTime) return false;

      const delay = differenceInMinutes(l.takenTime, l.scheduledTime);
      return Math.abs(delay) <= 30; // Within 30 minutes = on time
    }).length;
  }
}

Voice Assistant Integration

Siri Shortcuts (iOS)

// src/services/voice/SiriShortcuts.ts
import { Siri } from 'react-native-siri-shortcut';

export class SiriShortcuts {
  /**
   * Setup Siri shortcuts
   */
  static async setupShortcuts(medications: Medication[]): Promise<void> {
    // General shortcuts
    await this.createGeneralShortcuts();

    // Medication-specific shortcuts
    for (const medication of medications) {
      await this.createMedicationShortcut(medication);
    }
  }

  /**
   * Create general shortcuts
   */
  private static async createGeneralShortcuts(): Promise<void> {
    const shortcuts = [
      {
        activityType: 'com.medapp.check-schedule',
        title: 'Check my medication schedule',
        userInfo: { action: 'check-schedule' },
        suggestedInvocationPhrase: 'Check my medications',
        isEligibleForSearch: true,
        isEligibleForPrediction: true,
      },
      {
        activityType: 'com.medapp.log-all-taken',
        title: 'Mark all medications as taken',
        userInfo: { action: 'log-all-taken' },
        suggestedInvocationPhrase: 'I took my medications',
        isEligibleForSearch: true,
        isEligibleForPrediction: true,
      },
      {
        activityType: 'com.medapp.view-adherence',
        title: 'View medication adherence',
        userInfo: { action: 'view-adherence' },
        suggestedInvocationPhrase: 'Show my medication adherence',
        isEligibleForSearch: true,
        isEligibleForPrediction: true,
      },
    ];

    for (const shortcut of shortcuts) {
      await Siri.donateShortcut(shortcut);
    }
  }

  /**
   * Create medication-specific shortcut
   */
  private static async createMedicationShortcut(
    medication: Medication
  ): Promise<void> {
    await Siri.donateShortcut({
      activityType: `com.medapp.take-${medication.id}`,
      title: `Take ${medication.name}`,
      userInfo: {
        action: 'log-medication',
        medicationId: medication.id,
      },
      suggestedInvocationPhrase: `I took ${medication.name}`,
      isEligibleForSearch: true,
      isEligibleForPrediction: true,
    });
  }
}

Conclusion

Building a comprehensive medication reminder app requires careful attention to scheduling algorithms, notification strategies, and adherence tracking. This guide has covered:

  • Smart Scheduling: Intelligent algorithms for complex medication schedules
  • Push Notifications: Cross-platform local and remote notifications
  • Adherence Tracking: Comprehensive logging and analytics
  • Analytics Dashboard: Visual progress tracking
  • Pharmacy Integration: Refill management and automation
  • Caregiver Coordination: Family alerts and monitoring
  • Gamification: Achievements and streak tracking
  • Voice Integration: Siri Shortcuts for hands-free logging

With these implementations, you can build a production-ready medication reminder app that significantly improves patient adherence and health outcomes.


Ready to launch your medication reminder app? Start with JustCopy.ai to clone proven medication adherence interfaces and customize them with smart scheduling and tracking features. Deploy your app in weeks, not months.

πŸš€

Build This with JustCopy.ai

Skip months of development with 10 specialized AI agents. JustCopy.ai can copy, customize, and deploy this application instantly. Our AI agents write code, run tests, handle deployment, and monitor your applicationβ€”all following healthcare industry best practices and HIPAA compliance standards.