Device features
Sensors, Files & Background Jobs
Pair BLE fitness sensors, receive FIT/GPX/TCX files, run jobs while closed
AppMintAppwrightBoth builders - the steps below are the same in each.
1Receive files from other apps (FIT, GPX, TCX, CSV, JSON…)#
Turn on 'Open Files With This App' in the wizard and your app appears in TWO places:
- Android's 'Open with' sheet when a user taps a file.
- The SHARE / EXPORT sheet other apps show - including sport devices exporting workout files (.fit, .gpx, .tcx). Most fitness apps export via Share, which is why apps that only register 'Open with' never show up there.
The file arrives in your web code as one event with the file name, MIME type, and contents. Text formats (gpx, tcx, csv, json, xml…) arrive as a ready-to-use string; binary formats (.fit) arrive base64-encoded. Max 5 MB.
// Listen once, anywhere in your page JS:
window.addEventListener('appmint:fileopen', function (e) {
const f = e.detail; // {name, mimeType, size, text | base64}
if (f.name.endsWith('.gpx') || f.name.endsWith('.tcx')) {
const xml = new DOMParser().parseFromString(f.text, 'text/xml');
const points = xml.querySelectorAll('trkpt, Trackpoint');
console.log('workout with ' + points.length + ' points');
} else if (f.name.endsWith('.fit')) {
// .fit is binary — decode base64 to bytes, then parse
const bytes = Uint8Array.from(atob(f.base64), c => c.charCodeAt(0));
parseFit(bytes); // e.g. with the fit-file-parser npm package
}
});2Pair Bluetooth fitness sensors (heart rate, speed, cadence…)#
Turn on the Bluetooth permission in the wizard and your app can pair standard BLE sensors - every compliant chest strap and bike pod uses the same official Bluetooth profiles, so no per-brand code is needed:
- Heart rate straps → bpm
- Speed/cadence pods → km/h + pedal rpm
- Power meters → watts
- Running footpods → pace + steps/min
- Every sensor's battery level
Flow: scan (15s) → user picks a device → connect. Data then streams as events. Speed needs your wheel size; set it once (default 2096mm = 700x23c road wheel).
// 1. Scan — each found sensor fires a 'device' event:
window.addEventListener('appmint:ble', function (e) {
const m = e.detail;
if (m.kind === 'device') {
// {address, name, sensors:['heart_rate','speed_cadence',...], rssi}
addToPickerUI(m);
}
if (m.kind === 'data') {
if (m.type === 'heart_rate') showBpm(m.data.bpm);
if (m.type === 'speed_cadence') {
if (m.data.speedKmh) showSpeed(m.data.speedKmh.toFixed(1));
if (m.data.cadenceRpm) showCadence(Math.round(m.data.cadenceRpm));
}
if (m.type === 'power') showWatts(m.data.watts);
if (m.type === 'battery') showBattery(m.data.percent);
}
if (m.kind === 'error') console.warn('BLE:', m.error);
});
WebToApk.bleSetWheelCircumference(2105); // your wheel, in mm
WebToApk.bleStartScan();
// 2. When the user taps a device from your picker:
WebToApk.bleConnect(device.address);
// 3. Done training:
WebToApk.bleDisconnect();3Background jobs & durable auto-save (WorkManager)#
JavaScript cannot run while your app is closed - that is Android, not Appwright. What CAN run is a native delivery job your page hands to the OS. Two tools:
- workEnqueueOnline - durable one-shot delivery. Perfect auto-save: hand over the data and it POSTs to your server when there is network, retrying with backoff, surviving app kills and reboots. Re-using the same job id replaces the pending save with the newest state.
- workSchedulePeriodic - a recurring native HTTP call (sync trigger, heartbeat). Android's minimum interval is 15 minutes; shorter values are clamped.
Both deliver YOUR payload to YOUR URL - the server side is a normal endpoint you already have (Supabase edge function, your API…).
// Durable auto-save: call this on every important change.
function autoSave(state) {
WebToApk.workEnqueueOnline(
'autosave', // same id → newest state wins
'https://api.example.com/save',
'POST',
JSON.stringify({ 'Authorization': 'Bearer ' + token }),
JSON.stringify(state)
);
}
// Recurring sync every 30 minutes, even if the app is closed:
WebToApk.workSchedulePeriodic(
'sync', 'https://api.example.com/sync', 'POST', '{}', '', 30
);
// Manage jobs:
WebToApk.workCancel('sync');
WebToApk.workList('req1');
window.addEventListener('appmint:work', function (e) {
// {id, status:'enqueued'|'scheduled'|'cancelled'|'failed'}
// or {requestId, jobs:[{id, state, attempts}]}
console.log('work:', e.detail);
});4Checklist & common mistakes#
- Files: the toggle must be ON at build time - it registers your app in Android's manifest, which cannot change after install. Rebuild after enabling.
- BLE: the Bluetooth permission toggle must be ON at build time. The FIRST scan asks the user for the runtime permission (Android 12+: 'Nearby devices'; older: Location - that is an Android rule for BLE scanning, your app does not read location).
- BLE: pair in YOUR app, not in Android's Bluetooth settings - BLE fitness sensors are not classic paired devices.
- Background jobs: don't schedule a periodic job to 'run my page code' - it delivers HTTP to your server. If you need on-device processing while closed, that is what the delivery endpoint is for.
- Auto-save: always the SAME job id. Different ids queue up multiple stale saves.