Device features
Sensors, Files & Background Jobs
Pair BLE fitness sensors, receive FIT/GPX/TCX files, run jobs while closed
1Receive files, photos, videos and links from other apps#
Turn on 'Receive Shares From Other Apps' in the wizard and tick what your app accepts - text and links, data files (FIT, GPX, TCX, CSV, JSON, XML…), photos, videos, audio, documents. Your app appears in TWO places, for exactly those kinds:
- 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.
Ask for the file with AppMint.getOpenedFile() - it waits for you, however late your page starts. f.kind is image, video, audio, document or data. Text formats (gpx, tcx, csv, json, xml…) arrive as a ready-to-use f.text; small binaries (.fit) as f.base64; every data file and document as a real File in f.file. Photos, videos and audio arrive as f.url, a streamable address for <video src> / <img src>, with f.getFile() when you need the bytes. There is no size limit. A shared link or caption is read with WebToApk.getSharedText().
// Ask on startup — the file waits, however late your page mounts
const f = await AppMint.getOpenedFile(); // null if opened normally
if (f) {
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 — a real File; parse its bytes
parseFit(new Uint8Array(await f.file.arrayBuffer())); // e.g. fit-file-parser
} else if (f.kind === 'video') {
document.querySelector('video').src = f.url; // streams from disk, seekable
}
}
// A shared link or caption
const shared = JSON.parse(WebToApk.getSharedText() || 'null');
if (shared) console.log('shared:', shared.url || shared.text);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 AppMint. 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.