VetDrugs Apple Watch Integration Guide
December 2, 2025 · View on GitHub
Quick Start
This guide shows how to integrate CapacitorWatchEnhanced into your VetDrugs app to enable Apple Watch drug calculations.
Step 1: Build and Install the Plugin
# Build the enhanced plugin
cd /home/user/CapacitorWatchEnhanced/packages/capacitor-plugin
pnpm install
pnpm run build
# Install in VetDrugs
cd /home/user/VetDrugs
npm install file:///home/user/CapacitorWatchEnhanced/packages/capacitor-plugin
npx cap sync ios
Step 2: Update Your JavaScript Integration
Replace your existing watchHomeIntegration.js with this working version:
// src/js/watch/watchHomeIntegration.js
import { Watch } from '@macsupport/capacitor-watch-enhanced';
import { Capacitor } from '@capacitor/core';
import { getSavedPages } from '../drugServices.js';
/**
* Initialize Apple Watch message listeners for VetDrugs
* @param {Object} context - Home page context with loadSelection, setWeight, getSelectedItems
*/
export async function initializeWatchHomeListeners(context) {
if (!Capacitor.isNativePlatform()) {
console.log('⌚ Skipping watch - not on native platform');
return;
}
console.log('⌚ Checking watch availability...');
// Check watch status
try {
const info = await Watch.getInfo();
console.log('⌚ Watch Info:', info);
if (!info.isSupported) {
console.log('⌚ WatchConnectivity not supported on this device');
return;
}
if (!info.isPaired) {
console.log('⌚ No Apple Watch paired');
return;
}
if (!info.isWatchAppInstalled) {
console.log('⌚ VetDrugs watch app not installed');
return;
}
console.log('✅ Watch app available - initializing listeners');
} catch (error) {
console.error('⚠️ Failed to check watch status:', error);
return;
}
// ===================================================================
// PRIMARY LISTENER: Handle all watch messages
// ===================================================================
Watch.addListener('messageReceived', async (message) => {
console.log('📱 Received message from watch:', message);
try {
switch (message.type) {
case 'requestPresetList':
await handleRequestPresetList();
break;
case 'loadPresetAndCalculate':
await handleLoadPresetAndCalculate(message.payload, context);
break;
case 'updateWeightOnly':
await handleWeightUpdate(message.payload, context);
break;
default:
console.log('⚠️ Unknown message type:', message.type);
}
} catch (error) {
console.error('❌ Error handling watch message:', error);
}
});
// ===================================================================
// REACHABILITY LISTENER: Track watch connection status
// ===================================================================
Watch.addListener('reachabilityChanged', (data) => {
if (data.isReachable) {
console.log('✅ Watch is now reachable');
} else {
console.log('⚠️ Watch not reachable - messages will be queued');
}
});
// ===================================================================
// USER INFO LISTENER: Handle background data transfers
// ===================================================================
Watch.addListener('userInfoReceived', async (userInfo) => {
console.log('📱 Received background transfer from watch:', userInfo);
// Handle same as messageReceived
if (userInfo.type === 'requestPresetList') {
await handleRequestPresetList();
}
});
console.log('✅ VetDrugs watch listeners initialized successfully');
}
// ===================================================================
// MESSAGE HANDLERS
// ===================================================================
/**
* Handle request for list of saved drug calculation pages
*/
async function handleRequestPresetList() {
console.log('⌚ Handling preset list request from watch');
try {
// Fetch saved pages from Firestore
const savedPages = await getSavedPages();
// Format for watch display
const watchPageList = savedPages.map(page => ({
id: page.id,
name: page.name,
drugCount: page.drugs?.length || 0,
species: page.species || 'unknown'
}));
console.log(`⌚ Sending ${watchPageList.length} pages to watch`);
// Send to watch via application context (latest-value-only)
await Watch.updateApplicationContext({
data: {
type: 'presetPageList',
pages: watchPageList,
totalPages: watchPageList.length,
lastUpdated: Date.now()
}
});
console.log('✅ Preset list sent to watch successfully');
} catch (error) {
console.error('❌ Failed to send preset list:', error);
// Send error to watch
await Watch.updateApplicationContext({
data: {
type: 'error',
message: 'Failed to load drug pages',
error: error.message
}
});
}
}
/**
* Handle request to load a preset page and calculate drug doses
* @param {Object} payload - { pageName, weight, unit }
* @param {Object} context - Home page context
*/
async function handleLoadPresetAndCalculate(payload, context) {
const { pageName, weight, unit } = payload;
console.log(`⌚ Loading preset "${pageName}" with ${weight} ${unit}`);
try {
// Step 1: Load the drug page
console.log('⌚ Step 1: Loading drug page...');
await context.loadSelection(pageName);
await delay(500); // Allow page to load
// Step 2: Set patient weight
console.log(`⌚ Step 2: Setting weight to ${weight} ${unit}...`);
context.setWeight(weight, unit);
await delay(300); // Allow calculations to complete
// Step 3: Get calculated results
console.log('⌚ Step 3: Extracting calculated doses...');
const selectedItems = context.getSelectedItems();
const calculatedDoses = extractDrugDoses(selectedItems, weight, unit);
console.log(`⌚ Calculated ${calculatedDoses.length} drug doses`);
// Step 4: Send results to watch
await Watch.updateApplicationContext({
data: {
type: 'calculationResults',
payload: {
pageName,
weight: `${weight} ${unit}`,
drugCount: calculatedDoses.length,
drugs: calculatedDoses,
timestamp: Date.now()
}
}
});
console.log('✅ Drug calculations sent to watch successfully');
} catch (error) {
console.error('❌ Failed to calculate drugs:', error);
// Send error to watch
await Watch.updateApplicationContext({
data: {
type: 'error',
message: 'Failed to calculate drug doses',
error: error.message
}
});
}
}
/**
* Handle request to update weight and recalculate
* @param {Object} payload - { weight, unit }
* @param {Object} context - Home page context
*/
async function handleWeightUpdate(payload, context) {
const { weight, unit } = payload;
console.log(`⌚ Updating weight to ${weight} ${unit}`);
try {
// Update weight
context.setWeight(weight, unit);
await delay(300); // Allow recalculation
// Get updated results
const selectedItems = context.getSelectedItems();
const calculatedDoses = extractDrugDoses(selectedItems, weight, unit);
// Send updated results to watch
await Watch.updateApplicationContext({
data: {
type: 'calculationResults',
payload: {
weight: `${weight} ${unit}`,
drugCount: calculatedDoses.length,
drugs: calculatedDoses,
timestamp: Date.now()
}
}
});
console.log('✅ Recalculated doses sent to watch');
} catch (error) {
console.error('❌ Failed to update weight:', error);
}
}
// ===================================================================
// UTILITY FUNCTIONS
// ===================================================================
/**
* Extract calculated drug doses from selected items
* @param {Array} selectedItems - Array of selected drug items
* @param {Number} weight - Patient weight
* @param {String} unit - Weight unit (kg/lbs)
* @returns {Array} Array of formatted drug doses for watch display
*/
function extractDrugDoses(selectedItems, weight, unit) {
return selectedItems.map(item => {
// Extract relevant calculation data
// Adjust based on your actual data structure
return {
drugName: item.drugName || item.name,
dose: item.calculatedDose || `${item.doseValue} ${item.doseUnit}`,
volume: item.volume || null,
route: item.route || 'IV',
frequency: item.frequency || 'Once',
concentration: item.concentration || null,
notes: item.notes || null
};
});
}
/**
* Delay helper for async operations
* @param {Number} ms - Milliseconds to delay
*/
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Step 3: Initialize in home.svelte
Update your home.svelte to call the watch integration on mount:
// src/pages/home.svelte
<script>
import { onMount } from 'svelte';
import { initializeWatchHomeListeners } from '../js/watch/watchHomeIntegration.js';
// ... existing code ...
onMount(() => {
// Initialize watch communication
initializeWatchHomeListeners({
loadSelection: (name) => {
// Your existing loadSelection function
return loadSelection(name);
},
setWeight: (weight, unit) => {
// Your existing setWeight function
// Make sure it triggers recalculation
patientWeight = weight;
weightUnit = unit;
// Trigger calculations...
},
getSelectedItems: () => {
// Return currently selected drugs with calculated doses
return selectedDrugs; // Your selected items array
}
});
});
</script>
Step 4: Test on Device
-
Build and run on iPhone:
cd /home/user/VetDrugs npm run build-cap npx cap sync ios npx cap open ios -
In Xcode:
- Select your iPhone as target device
- Build and run (Cmd+R)
-
Check console logs: Look for these logs confirming initialization:
⌚ Checking watch availability... ⌚ Watch Info: { isSupported: true, isPaired: true, ... } ✅ Watch app available - initializing listeners ✅ VetDrugs watch listeners initialized successfully -
Test from watch:
- Open VetDrugs watch app
- Request preset list
- Check iPhone console for:
📱 Received message from watch: { type: 'requestPresetList' } ⌚ Handling preset list request from watch ✅ Preset list sent to watch successfully
Step 5: Verify Communication
Test Sequence:
- Watch sends:
{ type: 'requestPresetList' } - iPhone logs:
📱 Received message from watch... - iPhone sends back: Preset list via
updateApplicationContext() - Watch receives: List of drug pages
Then:
- Watch sends:
{ type: 'loadPresetAndCalculate', payload: { pageName: 'Dogs - Emergency', weight: 5.2, unit: 'kg' } } - iPhone logs:
⌚ Loading preset "Dogs - Emergency" with 5.2 kg - iPhone calculates doses
- iPhone sends back: Drug calculation results
- Watch displays: Calculated doses
Debugging
Enable Verbose Logging
Check Xcode console for these log patterns:
Plugin Loading:
📱 WatchPlugin loading...
📱 WatchPlugin loaded successfully
Message Reception:
📱 PHONE WatchDelegate didReceiveMessage: [type: requestPresetList]
📱 Received message from watch: { type: 'requestPresetList' }
Application Context Sent:
📱 WatchPlugin updateApplicationContext: [type: presetPageList, pages: [...]]
Common Issues
Issue: "Watch app not available" despite watch being paired
Solution: Check that your watch app bundle ID is [your.app.id].watchapp
Issue: Messages received on iPhone but JavaScript handler not called
Solution: Make sure Watch.addListener() is called BEFORE watch sends messages (use onMount)
Issue: "Watch not reachable" errors
Solution: Your watch is using transferUserInfo() instead of sendMessage(), which is fine - messages will be delivered via userInfoReceived listener
Message Format Reference
Watch → iPhone
// Request preset list
{
type: 'requestPresetList'
}
// Load and calculate
{
type: 'loadPresetAndCalculate',
payload: {
pageName: 'Dogs - Emergency',
weight: 5.2,
unit: 'kg'
}
}
// Update weight only
{
type: 'updateWeightOnly',
payload: {
weight: 6.0,
unit: 'kg'
}
}
iPhone → Watch
// Preset list response
{
type: 'presetPageList',
pages: [
{
id: '123',
name: 'Dogs - Emergency',
drugCount: 5,
species: 'dog'
}
],
totalPages: 10,
lastUpdated: 1234567890
}
// Calculation results
{
type: 'calculationResults',
payload: {
pageName: 'Dogs - Emergency',
weight: '5.2 kg',
drugCount: 5,
drugs: [
{
drugName: 'Acepromazine',
dose: '2.6 mg',
volume: '0.26 ml',
route: 'IV',
frequency: 'Once',
concentration: '10 mg/ml'
}
],
timestamp: 1234567890
}
}
// Error response
{
type: 'error',
message: 'Failed to load drug pages',
error: 'Network timeout'
}
Performance Tips
- Use
updateApplicationContext()for results - Latest-value-only, won't queue up multiple results - Use
transferUserInfo()for important data - Queued delivery, won't be lost - Add delays between operations - Allow time for page loads and calculations
- Batch drug data - Send all calculated doses in single message
Next Steps
Once working:
- Add error handling for network timeouts
- Add loading indicators on watch
- Implement dose history/favorites
- Add voice input for weight on watch
Support
Issues? Open a ticket at: https://github.com/macsupport/CapacitorWatchEnhanced/issues
Include:
- Xcode console logs
- Watch app version
- iOS version
- Steps to reproduce