Skip to content

Client manager and study controller

SmartPhoneClientManager is a singleton and the main entry point to CAMS. It holds the SmartphoneStudy objects you add, and one SmartphoneStudyController per study. Its state is created, configured or disposed.

CAMS persists its state. Once a study is added, deployed and resumed, it is restored and resumed after an app restart, so the flow below runs once per study.

File What is in it
runtime/client_manager.dart SmartPhoneClientManager
runtime/client_repository.dart SmartphoneClientRepository: the studies and their stored state
runtime/study_controller.dart SmartphoneStudyController: runs one study and owns its DataManager

A study protocol is deployed via a DeploymentService.

// Use the on-phone deployment service.
DeploymentService deploymentService = SmartphoneDeploymentService();
// Create a study deployment using the protocol.
var status = await deploymentService.createStudyDeployment(protocol);

Runtime management of studies is handed by the client manager SmartPhoneClientManager.

The most simple configuration of the client manager is:

await SmartPhoneClientManager().configure();

However, the client manager can be configured in different ways:

  • registration: unique device registration for this phone.
  • deploymentService: deployment backend (default is local SmartphoneDeploymentService).
  • dataCollectorFactory: custom collector factory (default uses the standard DeviceController).
  • enableNotifications: show task notifications (true by default).
  • enableBackgroundMode: run sampling in background (true by default, Android only).
  • backgroundNotificationTitle / backgroundNotificationText: text for Android background notification.
  • askForPermissions: request package permissions automatically (true by default).
final client = SmartPhoneClientManager();
await client.configure();

Once the client is configured, you can deploy studies to it. This is done by getting a study deployment from the deployment service, based on the protocol. In order to do this, we need to configure the client to use this deployment service. If the protocol has been added as shown above, this will add and deploy the study on the client:

// Use the on-phone deployment service.
DeploymentService deploymentService = SmartphoneDeploymentService();
// Create a study deployment using a protocol.
var status = await deploymentService.createStudyDeployment(protocol);
// Create and configure a client manager to use the deploymentService.
SmartPhoneClientManager client = SmartPhoneClientManager();
await client.configure(deploymentService: deploymentService);
// Add a study based on the deployed protocol to the client
final study = await client.addStudy(
SmartphoneStudy(
studyDeploymentId: status.studyDeploymentId,
deviceRoleName: phone.roleName,
),
);
/// Deploy the study to the client.
SmartPhoneClientManager().tryDeployment(
study.studyDeploymentId,
study.deviceRoleName,
);

This will configure the client manager and add a new study based on the specified protocol. However, if using the local SmartphoneDeploymentService, deploying and adding a study based on a protocol can be written more compact like this:

// Create and configure a client manager for this phone.
await SmartPhoneClientManager().configure();
// Create a study based on the protocol.
var study = await SmartPhoneClientManager().addStudyFromProtocol(protocol);
/// Deploy the study.
await SmartPhoneClientManager().tryDeployment(
study.studyDeploymentId,
study.deviceRoleName,
);

This can be written even more compact:

SmartPhoneClientManager().configure().then(
(_) => SmartPhoneClientManager()
.addStudyFromProtocol(protocol)
.then(
(study) => SmartPhoneClientManager().tryDeployment(
study.studyDeploymentId,
study.deviceRoleName,
),
),
);

Note that the client manager can handle multiple studies - just add more studies to the client:

var study_1 = await client.addStudyFromProtocol(protocol_1);
var study_2 = await client.addStudyFromProtocol(protocol_2);
var study_3 = await client.addStudyFromProtocol(protocol_3);

Data sampling can be controlled by the resume and pause methods:

// Resume data sampling for all studies added to the client.
SmartPhoneClientManager().resume();
// .. and pause all data sampling for all studies again.
SmartPhoneClientManager().pause();

Calling resume() or pause() on the client manager will resume or pause all studies added to the client. If you want more fine-grained control over each study, you can control each study using its SmartphoneStudyController:

SmartphoneStudyController? controller = client.getStudyController(study);
controller?.resume();

You can stop a study by calling stop which will permanently stop the study - once stopped, and study cannot be (re)started. Call dispose to dispose of the client - typically in the Flutter dispose method.

// Permanently stop the study.
// This will mark the study as stopped and remove it from the client manager.
SmartPhoneClientManager().stopStudy(
study.studyDeploymentId,
study.deviceRoleName,
);
// Dispose of the client (e.g. in Flutter dispose())
client.dispose();

All sampling data is available in the measurements stream. This stream is available on both controller and client:

// Listening on the measurements stream.
client.measurements.listen((measurement) {
// Do something with the measurement, e.g. print the json.
print(toJsonString(measurement));
});

Since it is a standard Dart Stream, you can subscribe, filter, map, etc. as needed.

// subscribe to the stream of measurements
var subscription = controller?.measurements.listen((Measurement measurement) {
// do something w. the measurement
...
});
...
// Cancel the subscription.
await subscription?.cancel();