Application Settings

Overview

Fitbit developers can make their application configurable by users, by creating an settings definition file which uses the Settings API. Users can configure an application within the Fitbit mobile application on their phone, and the settings can be sent to the Fitbit device using a Companion and Messaging.

Application Settings within Fitbit mobile application
Settings updated on Fitbit device

Application settings are written using React JSX syntax, compiled, then installed into the Fitbit mobile application during the application installation process.

How It Works

When a user changes configurable settings for an application, the values for each setting are persisted into settingsStorage on the mobile device.

The companion can either listen for a change event to determine which key/value has changed, or each key/value pair can be manually iterated on demand, or when the companion is started with launchReasons.settingsChanged

If required, the settings values can be passed from the companion to the device using the Messaging API.

Settings Definition

The settings definition file (/settings/index.jsx) can contain any of the following predefined JSX components:

  • Link
  • Text
  • TextImageRow
  • Button
  • Toggle
  • ColorSelect
  • Select
  • Additive List
  • Section
  • List
  • Slider

Multiple components are combined into a single file to produce a settings page.

The following definition will create a toggle button and a color picker:

function HelloWorld(props) {
  return (
    <Page>
      <Section
        title={<Text bold align="center">Demo Settings</Text>}>
        <Toggle
          settingsKey="toggle"
          label="Toggle Switch"
        />
        <ColorSelect
          settingsKey="color"
          colors={[
            {color: 'tomato'},
            {color: 'sandybrown'},
            {color: 'gold'},
            {color: 'aquamarine'},
            {color: 'deepskyblue'},
            {color: 'plum'}
          ]}
        />
      </Section>
    </Page>
  );
}

registerSettingsPage(HelloWorld);

App Settings

When a user configures an application, the changed settings need to be updated using the Companion, which (if required) can send the settings values to the App via Messaging.

By default, setting values are automatically persisted within settingsStorage which uses LiveStorage to emit an event that the Companion can consume.

Get Settings

Setting values can be retrieved using their key:

import { settingsStorage } from "settings";

// Get the value of the setting
let myKeyValue = settingsStorage.getItem("myKey");

Or they can be retrieved by their index:

import { settingsStorage } from "settings";

// Get the name of the first setting
let myKeyName = settingsStorage.key(0);

// Get the value of the first setting
let myKeyValue = settingsStorage.getItem(0);

Remove Settings

Setting values can be removed using their key:

import { settingsStorage } from "settings";

// Remove a setting from storage
settingsStorage.removeItem("myKey");

Clear Settings

If you need to remove all entries from settingsStorage you can use:

import { settingsStorage } from "settings";

// Clear all settings
settingsStorage.clear()

Handling Settings Changes

A user may change the settings of their application at any time, even if the application is not currently running. It's important for developers to handle situations where settings are changed in the following two ways:

Settings Change Event

The change event is automatically emitted when the user changes as setting, and the companion is currently running.

import { settingsStorage } from "settings";

// Event fires when a setting is changed
settingsStorage.onchange = function(evt) {
  // Which setting changed
  console.log("key: " + evt.key)

  // What was the old value
  console.log("old value: " + evt.oldValue)

  // What is the new value
  console.log("new value: " + evt.newValue)
}

Settings Changed Launch Reason

If the user changes application settings whilst the companion is not currently running, the companion will be automatically launched and the launchReason will be set to settingChanged.

import { me } from "companion";

if (me.launchReasons.settingChanged) {
  // Settings were changed while the companion was not running
}

Settings in Action

In the following example we will create an app which can receive color settings from a settings page using Messaging. When the user changes the setting, the new color value will be passed via the companion to the device and change the display color.

Application Settings within Fitbit mobile application
Settings updated on Fitbit device

Settings Definition

A basic definition of a color picker. The settings key is named myColor, and contains a list of named colors.

function Colors(props) {
  return (
    <Page>
      <Section
        title={<Text bold align="center">Color Settings</Text>}>
        <ColorSelect
          settingsKey="myColor"
          colors={[
            {color: 'tomato'},
            {color: 'sandybrown'},
            {color: 'gold'},
            {color: 'aquamarine'},
            {color: 'deepskyblue'},
            {color: 'plum'}
          ]}
        />
      </Section>
    </Page>
  );
}

registerSettingsPage(Colors);

Companion

The companion listens for the change event, and checks to see if settings were changed while it was not running, then sends the settings data via Messaging to the app.

import { settingsStorage } from "settings";
import * as messaging from "messaging";
import { me } from "companion";

let KEY_COLOR = "myColor";

// Settings have been changed
settingsStorage.onchange = function(evt) {
  sendValue(evt.data.key, evt.data.newValue);
}

// Settings were changed while the companion was not running
if (me.launchReasons.settingChanged) {
  // Send the value of the setting
  sendValue(KEY_COLOR, settingsStorage.getItem(KEY_COLOR));
}

function sendValue(key, val) {
  if (val) {
    sendSettingData({
      key: val
    });
  }
}
function sendSettingData(data) {
  // If we have a MessageSocket, send the data to the device
  if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
    messaging.peerSocket.send(data);
  } else {
    console.log("No peerSocket connection");
  }
}

App

For our app, we will change the color of a <rect> element.

<svg viewport-fill="white">
  <rect id="myElement" width="90%" height="90%" x="5%" y="5%" fill="black" />
</svg>

Next we will listen for the onmessage event, then set the fill color of our <rect>.

import * as messaging from "messaging";
import document from "document";

let myElement = document.getElementById("myElement");

messaging.peerSocket.onmessage = function(evt) {
  myElement.style.fill = evt.data.myColor;
}