Links: 3-28-2011

Links: 3-24-2011

  • Caterina.net» Blog Archive » FOMO and Social Media
    Quote: "FOMO is a great motivator of human behavior, and I think a crucial key to understanding social software, and why it works the way it does. Many people have studied the game mechanics that keep people collecting things (points, trophies, check-ins, mayorships, kudos). Others have studied how the neurochemistry that keeps us checking Facebook every five minutes is similar to the neurochemistry fueling addiction. Social media has made us even more aware of the things we are missing out on. You’re home alone, but watching your friends status updates tell of a great party happening somewhere. You are aware of more parties than ever before. And, like gym memberships, adding Bergman movies to your Netflix queue and piling up unread copies of the New Yorker, watching these feeds gives you a sense that you’re participating, not missing out, even when you are."
    (categories: culture psychology socialsoftware technology facebook twitter fomo people )

Playing with the Fitbit API

A little over a week ago I had major back surgery (spinal fusion of L4-L5 and a laminectomy on both L1 and L2) and as part of my recovery plan I bought a Fitbit so that I could game myself, by which I mean that I’d have something to measure (daily steps) as part of my recovery plan, which I have been since I got home last Thursday after a three night stay in the hospital. The first couple days I could barely walk (5 minutes every 5 hours) but since then I’ve really been improving on the walking side and by doing my 3x daily workout plan provided by the hospital (each workout takes about 1.5 hours including 10 minutes of walking), all of which I’m recording via the fitbit website as “activities” in addition to recording my sleep (happens automatically via the device), the steps I take per day (ditto) and my weight.

One of the things that I started in December, after reading about it on Rafe’s site, was the notion of using a spreadsheet to record your weight so that you could see your trend of weight over time, the thinking (and reality for me) being that your weight fluctuates up and down every day but if you’re doing things right, you’ll slowly start to lose weight which will show up on the graph as a downward trend. I slowly ballooned up to 210lbs over the last couple years as a result of eating poorly, no exercise (I stopped walking nightly because my legs would go numb / have sharp shooting pains, see above back surgery) and being busy at work and since putting the line diet in place back on December 1st, I’ve lost 14 pounds. And since the Fitbit website has web version of the line diet and an API I thought it would make sense to try and take all the date –> weight pairs in my Excel spreadsheet and pump them into the Fitbit website so that I wouldn’t be duplicating data. Because it was a one time thing I wanted to do it from the command line at first (maybe I’ll build a web application later) but it ended up only taking me a couple days (I can only sit in front of the computer for 30 minutes at a time) and I finally got it working last night. Here’s a rundown of the path I took:

1) Visited https://dev.fitbit.com/ to get started. It’s been so long since I did anything non-Jive, was interesting to see how developer communities are structured (or not structured? one web app / main page for app registration / description of the API, a Confluence instance for documentation and a Google Group for discussions) but step 1 (registering my application with fitbit.com) was really easy. Got my consumer key and consumer secret and I was off and running.

2) I downloaded the fitbit Java library, as well as the required commons logging, joda-time and JSON Java libraries. Ran into an issue downloading the JSON lib, which recently got moved over to Github but had everything downloaded and a project started in Eclipse in a couple minutes.

3) Next, I headed over to the client reference app page and read through that and the OAuth Authentication documentation. Couple problems I ran into with the client reference app:

a) it assumed a web-based application (specifically one built on top of Stripes) and I was building a desktop / command line client and

b) they provided the source code for the example application but then included source code snippets from the Java client, which they (Fitbit) do not provide the source code for. There were a number of times where I wanted to see how something was done in the client but couldn’t because the source isn’t available. Would be helpful (IMHO) to see that posted (and I asked as much on the Confluence doc page).

c) the client reference app source code shows the initialization of the Fitbit client happening like this:

client =
new FitbitAPIClientService(
getApiBaseUrl(),
getFitbitSiteBaseUrl(),
getEnvironmentPrefix() + "fitbit-example-client-application",
"e388e4f4d6f4cc10ff6dc0fd1637da370478e49e2",
credentialsCache,
entityCache,
subscriptionStore
);

but the it looks like the Fitbit Java API had since changed to require an instance of FitbitApiClientAgent as part of the constructor.

4) After wading through the docs, I wanted to sling some code… first step, since I was writing a desktop / command line client, was to request that my user account on fitbit.com grant my application the ability to read / write to it. Again, since I was doing this all manually, the code I needed looked like this:


String userID = "224B2Q";
LocalUserDetail ud = new LocalUserDetail(userID);
String url = client.getResourceOwnerAuthorizationURL(ud, "http://cephas.net/projects/fitbit/callback");

and then I printed out the resulting URL, which looks like this (and is now expired so I don’t mind showing you here):

http://www.fitbit.com/oauth/authorize?oauth_token=80ea75550b338a754f1d1a5393ac561c

copied that into my browser and the resulting page (after clicking the “Allow” button), contained a PIN which I then needed to programmatically submit back to Fitbit.com to complete the process:


AccessToken at = agent.getOAuthAccessToken(
"80ea75550b338a754f1d1a5393ac561c", // <-- oauth_token created in step1
consumerSecret, // <-- consumer secret created via registration of app
"6gelilrnqoggdkkqfss3ombodl"); // <-- PIN returned via web page

which resulted in a OAuth token and token secret that I could then use to make any requests for my user going forward.

5) One of the confusing things about the API / examples: most of the example application API invocations are doing using the FitbitAPIClientService class, which is relatively limited (only a couple useful methods, none for modifying / deleting data) versus the FitbitApiClientAgent, which contains the kitchen sink. Either way, the first thing I tried to do was to simply retrieve my activities using code that looked like this (reminder that if this was backed by a web app that I'd be retrieving the oauth token and secret from a persistent store and not just hardcoding in the construction):

APIResourceCredentials creds = new APIResourceCredentials(userID, "", "");
creds.setAccessToken(oauth_token);
creds.setAccessTokenSecret(oauth_secret);
client.saveResourceCredentials(ud, creds);
Activities o = client.getActivities(ud, new LocalDate());

which resulted in this:


Exception in thread "main" java.lang.NullPointerException at com.fitbit.api.client.FitbitApiClientAgent.setAccessToken(FitbitApiClientAgent.java:694) at com.fitbit.api.client.FitbitApiClientAgent.getCollectionResponseForDate(FitbitApiClientAgent.java:456) at com.fitbit.api.client.FitbitApiClientAgent.getActivities(FitbitApiClientAgent.java:203) at com.fitbit.api.client.FitbitApiClientAgent.getCollectionForDate(FitbitApiClientAgent.java:477) at com.fitbit.api.client.service.FitbitAPIClientService.getCollectionForDate(FitbitAPIClientService.java:134) at com.fitbit.api.client.service.FitbitAPIClientService.getActivities(FitbitAPIClientService.java:118)

This is the point at which I wish I had had source code access for the fitbit Java client but short of that, my guess was that the FitbitApiClientAgent (which the FitbitAPIClientService contained a reference to) wasn't being given the ResourceCredentials necessary to make the request, so I changed the initialization code so that the agent was directly made aware of the resource credentials for my user:


APIResourceCredentials creds = new APIResourceCredentials(userID, "", "");
creds.setAccessToken(oauth_token);
creds.setAccessTokenSecret(oauth_secret);
client.saveResourceCredentials(ud, creds); // <-- apparently wasn't getting through to the agent
agent.getCredentialsCache().saveResourceCredentials(ud, creds); // <-- key: had to do this
Activities o = client.getActivities(ud, new LocalDate());

which solved the problem. Next, since the end goal was saving a bunch of weight values of a period of a couple months, I thought it would be good to attempt to retrieve my weight:


double currentWeight = agent.getWeight(ud, new FitbitUser(userID), new LocalDate());

which worked... except my weight was coming back in kilograms. Apparently they default to using the metric system for any request that doesn't contain this:


agent.setRequestHeader("Accept-Language", "en_US");

except there's a bug with that where either the client isn't sending that header along or the server isn't honoring that header, details were available in the discussion forums. Really not that big of a deal though, relatively easy to convert between kilograms and pounds so finally...

6) Loop over the date / weight value pairs in the CSV file export, reading it line by line and set my weight for each date:


File file = new File(args[1]);
BufferedReader isr = new BufferedReader(new FileReader(file));
String line = "";
while ((line = isr.readLine()) != null) {
String[] parts = line.split(",");
String date = parts[0];
String weight = parts[1];
String[] dateParts = date.split("/");
LocalDate d = new LocalDate(Integer.parseInt(dateParts[2]),
Integer.parseInt(dateParts[0]),
Integer.parseInt(dateParts[1]));
Float newWeight = Float.parseFloat(weight) * new Float(0.45359237); // convert pounds to kilograms
agent.logWeight(ud, newWeight, d);
}

and everything worked just like it should.

Overall I'm been really please with the Fitbit: it's given me something to measure on a daily basis (easy) but it does it all in the background without me really having to do much at all (hard) and they've built a TON of functionality into the site (I'm using maybe 1/4 of the functionality). Being able to enter my weight has been great (easy to enter your weight via your mobile device in the morning after you weight yourself) and has replaced the Excel / Line Diet thing I was doing before. I already dropped it (the little Fitbit device that you clip to your pants / shirt) in the toilet once by accident (ha!) but it didn't skip a beat. The API has a couple wrinkles, mostly around the examples and documentation, which isn't all that surprising since it's brand new and currently listed as "beta". The specific things I'd want to see improved in / around the API:

  • get rid of the examples based on Stripes and concentrate more on the code necessary to 1) create a web application and 2) a mobile / desktop application especially as it relates to OAuth (since the workflow is different between each)
  • trim down the fitbit Java client: there are 89 classes in the jar, no JavaDoc (that I could find) on any of them, no source and very few examples outside of the Stripes web application which, as I mentioned above, didn't use the agent API at all
  • could go either way on this one but the requirement that you set a request header to determine which unit system you want to use seems like it might make more sense to be part of the API, ie: it would be clearer if the API simple forced you to declare which unit of measure of you wanted to use on the method, agent.logWeight(user, weight, date, "pounds"), but don't feel that strongly about that one.

and the only thing so far with the tracker that I'd like to see improved is the interface for starting / stopping sleep. I might have just run into the going to bed after midnight issue but it's not entirely obvious when I start / stop something.

Well worth the $99 if you're thinking about buying one.