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.
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
- Core Architecture
- Data Models
- Smart Scheduling Engine
- Push Notification Implementation
- Adherence Tracking
- Analytics Dashboard
- Pharmacy Integration
- Caregiver Coordination
- Gamification
- 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.
Related Articles
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.