Writing JS apps for Fitbit Ionic

Writing JS apps for Fitbit Ionic

Written by Charles Grugan and edited by Aaron Careaga

My motivation

Let me tell you a story about sailing with my wife, Audrey. She and I have different opinions of what constitutes a good time. For me, I love sailing together, for her, not so much. So when I ask her to take over for a second so that I can close out of my navigational charts on the iPad and open a different instrument panel view to see how fast we're going, she gets a bit nervous and has less of a good time. When I get curious about what our current speed is, I have to choose between frustrating Audrey by asking her to take control, or not know how fast we're moving rendering myself unable to tell her how much longer we'll be out there. Both scenario's cause frustration for both of us.

The Solution

I had been going back and forth on whether I wanted the new Fitbit Ionic (a waterproof fitness tracker) or graduate up to Apple's iWatch. After reading reviews, and thinking hard about the features I wanted and needed, I settled in on the Ionic. For my birthday, Audrey bought the Fitbit device for me. I was thrilled, but neither of us knew that it might be able to solve the issue of our sailing frustrations.

While digging around and looking for apps for the watch, I discovered that the app gallery for it had not even gone live yet. I also stumbled upon Fitbit's dev resources. From there I discovered how to register myself as a developer, integrate with their SDK, tools and studio editor, and within a day had written an application that would solve our real world problem.

I created an application that with the flick of my wrist would give me my current speed in Knots, and MPH along with my current compass heading by comparing my previous GPS location with my current. It's not the most perfect accuracy, but it allows for ballpark estimation while navigating a boat. This solved the little problem that Audrey and I had been experiencing for years. Now we're going to dig in to some of what I learned while working on the app called SaltyDog.


The Fitbit Development Environment

Before we can do anything else, there is an unfortunate limitation (among other limitations) to developing for the Ionic, you need to have one of the devices. At the time of writing, there is no simulator or emulator capable of running the apps that you make for the Fitbit.

Dependencies and helpful tools

Fitbit studio

Fitbit studio is the web based editor and debug tool for developing and adjusting Fitbit ionic apps.

alt text

App architecture

The folder structure of an app or a watchface for the Fitbit Ionic app may seem familiar to you, especially if you've made anything using Node.js or React before. There are a few exceptions, mainly being that there are special directories created if your app has configuration options available in a settings menu (which lives on your mobile devices' Fitbit app), or a companion app that will run concurrently with a user's mobile device.

Read more on Fitbit app architecture here

A different kind of DOM

For the most part, you can select objects for styling in your CSS or manipulate them through JavaScript using ID's or class names, but this isn't the DOM that most of us are used to.

Fitbit relies exclusively on using SVG's to render anything visible on screen. Your SVG becomes your DOM. The SVG that we're talking about lives inside of your project's index.gui file. There is another .gui file called widgets.gui where we can allow access to certain Fitbit OS systemwide components to be used within an applications. widgets.gui is great to use if you'd like to create a quick app using Fitbit's already designed UI elements.

Read more on SVGs for Fitbit


Styling with CSS

This should feel pretty familiar to any web developer as the process is very much the same. CSS for the most part is read by the Fitbit app renderer in a very similar manner to a browser's approach for interpretation. There are a few minor differences though.

Gotchas:

  • For Fitbit CSS, unlike CSS for web, don't use units such as px for values to properties such as font-size, or letter-spacing.
  • EG:

    #myElement: { font-size: 20; letter-spacing: 10; }

    • Use the fill property instead of color to add color to text elements , this falls in line with the standards of coloring objects with CSS that reside within and SVG.
  • EG:

    / use fill instead of color to add color rules to your test / #myElement: { fill: #FCFCFF; }

    • When aligning or justifying your text, the standard CSS property text-align will not work. Instead use text-anchor.
  • EG:

    / use text-anchor instead of text-align to align you bodies of text / #myElement: { text-anchor: start | middle | end; }

  • Alternately, you can use some of the systemwide text components and take advantage of the system's UI rules and styling. Read more on using system text components
  • If you are in the habit of getting fancy with your CSS selectors, you're in for some disappointment with Fitbit's CSS selector limitations. Child, Descendant, and Attribute selectors are not supported, meaning the following examples will NOT work:
  • DON'T TRY THESE:

    #myElement .child: { fill: #FCFCFC; border-radius: 10; }

    #myElement > ul: { font-size: 20; letter-spacing: 10; }

    #myElement[selected="true"]: { fill: red; letter-spacing: 10; }

It's safe to assume that pseudo selectors like :before, :after and :first-of-type do not work either.

Read more on Fitbit's CSS guide

Linking CSS to your view

You'll want to handle linking your stylesheets to your view via your widgets.gui using a standard link tag.

      <svg>
        <defs>
          <link rel="stylesheet" href="styles.css" />
          <link rel="import" href="/mnt/sysassets/widgets_common.gui" />
        </defs>
      </svg>

Then in your index.gui, you can create your object tags within the SVG using IDs and class names which you can then use to select within your CSS.

    <!-- index.gui -->
    <svg id ="main" pointer-events="visible">
      <text id="myElement" class="body-copy"/> This is my text element
      </text>
    </svg>

    /* styles.css */
    #myElement: {
      font-size: 20;
      letter-spacing: 10;
      fill: #47A8FF;
    }


Manipulating with JS

Your main entry point file will be in the app directory named index.js. You can select your objects in JS the same as you do when writing JS for a browser.

    var myElement = document.getElementById('myElement')
    //or
    var myElement = document.querySelector('#myElement')

There is one major difference, you will not have access to the document object right away as you would in a browser. Therefore you MUST import the document model.

    import document from "document";
    
    var myElement = document.getElementById('myElement')
    //or
    var myElement = document.querySelector('#myElement')

Using the wearable's sensors

To make my SaltyDog application, I needed access to my Fitbit's geolocation data via it's GPS. There are many more sensors in the device that you can get access to, including heart rate, accelerometer, barometer and more. For the purposes of this article, we'll discuss geolocation only.

Check out the full documentation on available sensor data

At the top of my index.js, I can get access to this by importing geolocation.

    import { geolocation } from "geolocation";

Checkout Fitbit's Geolocation docs from their device API guide

In my app, I'm using geolocations' watchPosition() method. This allows the device to periodically send updated information when position values change.

    geolocation.watchPosition(
      successCallback: PositionCallback,
      errorCallback?: PositionErrorCallback | undefined,
      options?: PositionOptions | undefined);

What the watchPosition method returns is a position object that contains a coordinates object. In there we can find the good bits of information that we need to determine speed and compass heading.

Here is a breakdown of the position object:

    position = {
       timestamp: <time of the position reading>,
       cords:{
         speed: <speed in meters per second>
         heading: <compass heading 0 - 360>
       }
     }

To bring it all together, here is my index.gui

    <svg id ="main" pointer-events="visible">
      <text id="chron" class="sensor-data"/>
      <text id="sog-label" class="sensor-label">kts</text>
      <text id="sog-data" class="sensor-data"> ... </text>
      <text id="heading-label" class="sensor-label">hdg</text>
      <text id="heading-data" class="sensor-data"> ... </text>
    </svg>

You can see that I have 5 text elements, two labels, and two areas for dynamic data for speed and heading and one that is called chron that I use to display time of day (another source of frustration, I always lose track of time when sailing).

Using the IDs of each field, I give myself access to these text elements in my index.js

      let sogData = document.getElementById("sog-data");
      let sogLabel = document.getElementById("sog-label");
      let headingData = document.getElementById("heading-data");
      let chron = document.getElementById("chron");

I update their values every time that the geolocation module sends a position update event with an anonymous callback function.

      geolocation.watchPosition(function(position) {
      let data = {
        knots: {
          value: position.coords.speed ? getKnots(position.coords.speed) : 0,
          label: "kts"
        },
        mph: {
          value: position.coords.speed ? getMph(position.coords.speed) : 0,
          label: "mph"
        },
        heading: {
          value: position.coords.heading ? position.coords.heading.toFixed(2) : 0
        }
      };
    
      /*
      This block here looks at the value of a variable called 
      sogUnitOfMeasure (Speed over ground unit of measure). 
      This value toggles between `mph` and `knots` on tap of 
      the screen so the user can switch back and forth easily.
      */
      if (sogUnitOfMeasure === 'knots'){
          sogData.text = data.knots.value;
          sogData.x = 86;
          sogLabel.text = data.knots.label;
      } else {
          sogData.text = data.mph.value;
          sogData.x = 100;
          sogLabel.text = data.mph.label;
      }
      headingData.text = data.heading.value + "∞";
    
    })

I have two small methods that I should probably move to a utils.js file but for now it's stored right in my index.js. These do a conversion of speed from meters per second to both miles per hour and speed in knots.

    function getKnots(vertComp) {
      return (vertComp/0.5144456334).toFixed(1);
    }
    function getMph(vertComp) {
      return (vertComp/0.447039259).toFixed(1);
    }

Users can toggle between seeing their speed in knots or MPH with a tap of the screen.

    /**
     * `main` is the id for the whole SVG that wraps the
     * application view.
     */
    main.onclick = function(e){
      console.log("click");
      if (sogUnitOfMeasure === "knots"){
        sogUnitOfMeasure = "mph";
      } else {
        sogUnitOfMeasure = "knots";
      }
    };

Running the app on your device

One of the shortcomings that I found with developing an app for the Fitbit Ionic is that there is no emulator for the development environment. To test and debug your code, and even just to see small style tweaks, you have to get the application sideloaded onto a device. Thankfully, this process is made pretty easy as long as you have your Ionic and your development laptop on the same wi-fi with the developer bridge enabled and open in ionic's settings menu.

Shown below is the Ionic appearing in the device menu.

studio pairing to the ionic

Once you connect to your ionic by selecting it in the dropdown shown above, you'll see the word Ionic with a green dot next to it. You'll also see that the "Run" and "screenshot buttons are active."

ionic is connected and ready

Clicking "Run" will push your application to the Ionic device and open it up so you can see it working. Also, the debug console will start to output anything you print to the console and let you know when the app has finished installing and has started to run.

At that point, you can start making code changes, click "Run" again to update the app and see changes as you make them.


Publishing your apps

Running your app on the device will automatically trigger a build process, though you can do this yourself by clicking the "Build" button in the Fitbit Studio interface.

When you're happy with your progress, you can publish your app through Fitbit's Gallery App Manager (GAM).

The first step is making sure that you app builds and follows Fitbit's guidelines.

Then, you can click on "Publish" from the download dropdown menu. This will start an automatic download of your bundled application as a .fba file. This is what you will upload through the Gallery App Manager for review by the Fitbit team for integration into their app gallery.

ionic is connected and ready

Log in to the Gallery Apps Manager. Here you can upload screenshots of your app as well as write a description that will show up in the app gallery.

SaltyDog's app manager page

Scrolling down, you will find an area where you can upload your .fba file and add release notes.

After that point it just takes some waiting for the Fitbit development team to approve your app, or ask you to make a few adjustments and re submit. That process took about a week for me.

A brave, new, wearable world

So that was it. I spent a week or two stumbling around and getting my footing together enough to create a tool using my newly acquired Fitbit. My wife pointed out the irony that upon receiving a fitness tracker, I didn't go for a jog, I started coding.

The Fitbit Ionic is a pretty good place to start playing around with wearables, as it supports JS and CSS, both of which have a large base of developers and support from sites and communities like CSS-Tricks and Stack Overflow.

Wearable tech is not always the first thing that we think of when it comes to developing with Javascript. Pebble was one of the first, and now that they've been acquired by Fitbit, JS on a watch pairs a large base of developers to a pretty wide audience of wearables. There are other platforms that offer similar support in a small container, like Intel's Edison. Edison can be made small enough to work in a convenient and compact form as a smart wearable device and supports Node.js apps.

It's important that we as developers pay attention to this trend of small smart devices and start to identify when and where data collection and activity augmentation is appropriate and useful.

What started as a birthday present from my lovely wife, turned into something unexpected. A new platform for me to learn and develop for, as well as a solution to one of the many small frustrations that I cause my wife on an almost daily basis.

My hope is the wearable tech market will continue to evolve, creating a huge opportunity for JavaScript developers to enter this new world and solve even more problems that face us every day.

Please feel free to reach out to me with questions on twitter @che_effe.

CF Grugan is a UX Design Technologist at Comcast who works with UX Designers, Industrial Designers and UX researchers to help experiment and prototype future technologies and experiences.


Rethinking with React 16

Rethinking with React 16

May Cause Side Effects: How to Implement Redux Sagas as Middleware

May Cause Side Effects: How to Implement Redux Sagas as Middleware