Skip to content
CARP Docs
Ask AISystemLightDark
On this page

Quickstart

Build a complete study protocol that collects data from an Apple Watch.

Copy MarkdownOpen inOpen in GitHubView as MarkdownOpen in Scira AIOpen in ChatGPTOpen in ClaudeOpen in Cursor

This page builds a small but complete study: a phone, a watch connected to it, and a task that collects motion, heart rate, battery and sound from the watch.

Prerequisites

Complete Installation and add the companion watch app first.

Complete example

import 'package:carp_core/carp_core.dart' hide Smartphone;
import 'package:carp_mobile_sensing/carp_mobile_sensing.dart';
import 'package:carp_aware_package/carp_aware_package.dart';

void main() async {
  // 1. Register the package before anything else.
  SamplingPackageRegistry().register(AppleWatchSamplingPackage());

  // 2. Create a protocol.
  final protocol = SmartphoneStudyProtocol(
    ownerId: 'owner@dtu.dk',
    name: 'Apple Watch Sensing Example',
  );

  // 3. The devices: a phone, and a watch connected to it.
  final phone = Smartphone();

  final watch = AppleWatchDevice(
    motionSamplingRate: 10,
    heartRateEnabled: true,
    batteryEnabled: true,
    audioEnabled: true,
    fileTransferInterval: const Duration(minutes: 15),
    transferMode: WatchTransferMode.incremental,
    backgroundSessionType: WatchBackgroundSessionType.microphone,
  );

  protocol
    ..addPrimaryDevice(phone)
    ..addConnectedDevice(watch, phone);

  // 4. What to collect — added to the watch, not to the phone.
  protocol.addTaskControl(
    ImmediateTrigger(),
    BackgroundTask(measures: [
      Measure(type: AppleWatchSamplingPackage.MOTION),
      Measure(type: AppleWatchSamplingPackage.HEART_RATE),
      Measure(type: AppleWatchSamplingPackage.BATTERY),
      Measure(type: AppleWatchSamplingPackage.AMBIENT_NOISE),
      Measure(type: AppleWatchSamplingPackage.AUDIO_LABEL),
      Measure(type: AppleWatchSamplingPackage.DEVICE),
    ]),
    watch,
  );

  // 5. Deploy and start sampling.
  final client = SmartPhoneClientManager();
  await client.configure();
  final study = await client.addStudyFromProtocol(protocol);
  await client.tryDeployment(study.studyDeploymentId, study.deviceRoleName);
  client.resume();

  // 6. Everything collected — phone and watch alike — arrives here.
  client.measurements.listen(print);
}

How the example works

Register the package

SamplingPackageRegistry().register(AppleWatchSamplingPackage()) makes the nine watch measure types and the AppleWatchDevice known to CAMS.

Connect the watch to the phone

The phone is the primary device: it runs the study and uploads the data. The watch is a peripheral connected to the phone, and it sends its data to the phone.

protocol.addConnectedDevice(watch, phone);

Configure the watch

Sampling rates and which sensors run are properties of the AppleWatchDevice, not of the individual Measure. See Configuration.

Add the measures to the watch

protocol.addTaskControl(ImmediateTrigger(), BackgroundTask(...), watch);

Warning

Add the measures to watch, not to phone. If you add them to phone, the files still arrive from the watch, but nothing is converted into measurements.

Delivery and background runtime

fileTransferInterval: why your data is 15 minutes old

The watch buffers records locally and hands them over on a timer. With the default of 15 minutes, a measurement collected at 10:00 reaches your app around 10:15, together with everything else from that quarter hour.

A lower interval transfers data sooner but drains the battery faster.

backgroundSessionType: how the watch keeps collecting

watchOS suspends an app shortly after the wrist is lowered, unless the app holds a background runtime session. The default, microphone, starts a silent capture session that keeps the watch app alive without registering a workout.

The alternative, workout, runs longer and more reliably, but it appears in the participant's fitness apps and affects their activity rings. It can also result in App Store rejection.

Check the watch status

AppleWatchDeviceManager holds the live state of the watch connection: whether a watch is paired, whether the companion app is installed, and how transfers are going.

final watchManager =
    DeviceController().getDeviceManager(AppleWatchDevice.DEVICE_TYPE)
        as AppleWatchDeviceManager;

// Ready to collect? (paired + companion app installed)
if (!watchManager.watchStatus.isAvailable) {
  print('Ask the participant to install the watch app.');
}

watchManager.watchStatusEvents.listen((status) => print(status));
watchManager.fileTransferEvents.listen((transfer) => print(transfer));

See Runtime state for details.

Start collecting on the watch

  1. Open the watch app and press Start

    watchOS does not run a freshly installed app in the background before its first launch.

  2. Grant the permissions

    HealthKit for heart rate, microphone for sound. The prompts appear on the watch.

  3. Check the status

    AppleWatchStatus reports paired: true, appInstalled: true, collecting: true.

  4. Wait for the first transfer

    The first transfer arrives within fileTransferInterval. While testing, press Send data to phone on the watch to skip the wait.

Next steps

Configuration

Every setting on AppleWatchDevice, and what it costs.

Data types

The nine watch measure types and their fields.

Runtime state

Know whether the watch is paired, collecting and delivering.

Quick links

Page · DocumentationCARP DocumentationPage · DocumentationIntroductionPage · DocumentationQuickstartPage · DocumentationTerminologyPage · DocumentationArchitecture