I think Google Fit is a wonderful platform. It connects multiple devices and apps to give you¬†insight into your fitness. I connected a Withing Smart Body Analyzer scale to the system. What I’m missing is a simple min / max weight per day graph, something like this:

Google Fit Graph

This blog describes how to get the weight data out of Google Fit into your C# application. You can use your own chats solution to plot a graph like this.

1. Google Fit Nuget Package

Google was kind enough to create a C# wrapper around the Google Fit API and provided a Nuget package for us:

Install-Package Google.Apis.Fitness.v1»

2. Scope, credentials + service

Next you’ll need to get your credentials ready. How you do this is outside the scope of this tutorial as it depends on what platform you use.¬†The credential needs to be created with scope FitnessService.Scope.FitnessBodyRead.¬†The FitnessService needs a UserCredential. I’ve implemented the Google MVC Authentication flow that will give met a result that provides a credential:

var credential = {...} 
var service = new FitnessService(new BaseClientService.Initializer()
{
    ApplicationName = Assembly.GetExecutingAssembly().GetName().Name,
    HttpClientInitializer = credential
});

You can read more on Offline Google Authentication for MVC.Net here.

3. Base class for querying

Google Fit works with data sets that can be queried using a¬†DataSourceId and a¬†DataSourceType.¬†The query mechanism will¬†seem somewhat weird at first as it uses a myriad of objects. To make things easier, I’ve created an abstract¬†FitnessQuery¬†class. It facilitates the creation and execution of the queries on the data sets.

To make handling of time easier I’ve created the¬†GoogleTime class – it is appended at the end of the article.

namespace KeesTalksTech.Examples.GoogleFitApi.Queries
{
    using Google.Apis.Fitness.v1;
    using Google.Apis.Fitness.v1.Data;
    using System;

    /// <summary>
    /// Helper class that hides the complexity of the fitness API.
    /// </summary>
    public abstract class FitnessQuery
    {
        private FitnessService _service;
        private string _dataSourceId;
        private string _dataType;

        /// <summary>
        /// Initializes a new instance of the <see cref="FitnessQuery"/> class.
        /// </summary>
        /// <param name="service">The service.</param>
        /// <param name="dataSourceId">The data source identifier.</param>
        /// <param name="dataType">Type of the data.</param>
        public FitnessQuery(FitnessService service, string dataSourceId, string dataType)
        {
            _service = service;
            _dataSourceId = dataSourceId;
            _dataType = dataType;
        }

        /// <summary>
        /// Creates the request.
        /// </summary>
        /// <param name="start">The start.</param>
        /// <param name="end">The end.</param>
        /// <returns>The request.</returns>
        protected virtual AggregateRequest CreateRequest(DateTime start, DateTime end, TimeSpan? bucketDuration = null)
        {
            var bucketTimeSpan = bucketDuration.GetValueOrDefault(TimeSpan.FromDays(1));

            return new AggregateRequest
            {
                AggregateBy = new AggregateBy[] {
                    new AggregateBy
                    {
                        DataSourceId = _dataSourceId,
                        DataTypeName = _dataType
                    }
                },

                BucketByTime = new BucketByTime
                {
                    DurationMillis = (long)bucketTimeSpan.TotalMilliseconds
                },

                StartTimeMillis = GoogleTime.FromDateTime(start).TotalMilliseconds,
                EndTimeMillis = GoogleTime.FromDateTime(end).TotalMilliseconds

            };
        }

        /// <summary>
        /// Executes the request.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="userId">The user identifier.</param>
        /// <returns>
        /// The response.
        /// </returns>
        protected virtual AggregateResponse ExecuteRequest(AggregateRequest request, string userId = "me")
        {
            var agg = _service.Users.Dataset.Aggregate(request, userId);
            return agg.Execute();
        }
    }
}

4. Querying weight

Next I created the WeightQuery class. It will query data source derived:com.google.weight:com.google.android.gms:merge_weight using data type com.google.body..

namespace KeesTalksTech.Examples.GoogleFitApi.Queries
{
    using Google.Apis.Fitness.v1;
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Queries the weight data source of Google Fit.
    /// </summary>
    public class WeightQuery : FitnessQuery
    {
        private const string DataSource = "derived:com.google.weight:com.google.android.gms:merge_weight";
        private const string DataType = "com.google.weight.summary";

        /// <summary>
        /// Initializes a new instance of the <see cref="WeightQuery"/> class.
        /// </summary>
        /// <param name="service">The service.</param>
        public WeightQuery(FitnessService service) :
            base(service, DataSource, DataType)
        {
        }

        /// <summary>
        /// Queries the weight.
        /// </summary>
        /// <param name="start">The start.</param>
        /// <param name="end">The end.</param>
        /// <returns>A list with weight data points.</returns>
        public IList<WeightDataPoint> QueryWeight(DateTime start, DateTime end)
        {
            var request = CreateRequest(start, end);
            var response = ExecuteRequest(request);

            return response
                .Bucket
                .SelectMany(b => b.Dataset)
                .Where(d => d.Point != null)
                .SelectMany(d => d.Point)
                .Where(p => p.Value != null)
                .SelectMany(p =>
                {
                    return p.Value.Select(v =>
                        new WeightDataPoint
                        {
                            Weight = v.FpVal.GetValueOrDefault(),
                            Stamp = GoogleTime.FromNanoseconds(p.StartTimeNanos).ToDateTime()
                        });
                })
                .ToList();
        }

        /// <summary>
        /// Queries the weight.
        /// </summary>
        /// <param name="start">The start.</param>
        /// <param name="end">The end.</param>
        /// <returns>A list with weight data points.</returns>
        public IList<WeightDataPoint> QueryWeightPerDay(DateTime start, DateTime end)
        {
            return QueryWeight(start, end)
                .OrderBy(w => w.Stamp)
                .GroupBy(w => w.Stamp.Date)
                .Select(g => new WeightDataPoint
                {
                    Stamp = g.Key,
                    MaxWeight = g.Max(w => w.Weight),
                    MinWeight = g.Min(w => w.Weight),
                })
                .ToList();
        }
    }
}

I use LINQ to query the content of the response and convert it to a WeightDataPoint object. The QueryWeight(...) method returns a list with all the data points. The QueryWeightPerDay(...) will also return a list, but it will return a unique data point per day with a max and a min weight. Only the data part of the stamp will be used in this method.

namespace KeesTalksTech.Examples.GoogleFitApi.Queries
{
    using System;

    /// <summary>
    /// Weight data point.
    /// </summary>
    public class WeightDataPoint
    {
        /// <summary>
        /// Gets or sets the weight.
        /// </summary>
        /// <value>
        /// The weight.
        /// </value>
        public double? Weight { get; set; }

        /// <summary>
        /// Gets or sets the stamp.
        /// </summary>
        /// <value>
        /// The stamp.
        /// </value>
        public DateTime Stamp { get; set; }

        /// <summary>
        /// Gets or sets the maximum weight.
        /// </summary>
        /// <value>
        /// The maximum weight.
        /// </value>
        public double? MaxWeight { get; set; }

        /// <summary>
        /// Gets or sets the minimum weight.
        /// </summary>
        /// <value>
        /// The minimum weight.
        /// </value>
        public double? MinWeight { get; set; }
    }
}

5. Mixing it together in a data table

I’m using Google Charts to plot the graph. It uses a data table – basically jagged array. Again I’ll use LINQ to create the array.

if (this.Credential == null)
{
    return RedirectToAction("Index", "Home");
}

var service = new FitnessService(new BaseClientService.Initializer()
{
    ApplicationName = Assembly.GetExecutingAssembly().GetName().Name,
    HttpClientInitializer = Credential
});

var query = new WeightQuery(service);
var list = query.QueryWeightPerDay(DateTime.Now.AddDays(-60), DateTime.Now);

var dataTable = list
    .Select(w => new object[]
    {
        w.Stamp,
        w.MinWeight,
        w.MaxWeight
    })
    .ToArray();

This dataTable can be serialized to JSON and used to create a Google Chart.

Handling time

The Google API uses milliseconds and nanoseconds. In C# I like to work with simple DateTime structures, that why I created the GoogleTime class that helps with the conversion between the two formats:

namespace KeesTalksTech.Examples.GoogleFitApi
{
    using System;

    /// <summary>
    /// Helps converting between miliseconds since 1970-1-1 and C# <c>DateTime</c> object.
    /// </summary>
    public class GoogleTime
    {
        private static readonly DateTime zero = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        /// <summary>
        /// Gets the total milliseconds.
        /// </summary>
        /// <value>
        /// The total milliseconds.
        /// </value>
        public long TotalMilliseconds { get; private set; }

        /// <summary>
        /// Prevents a default instance of the <see cref="GoogleTime"/> class from being created.
        /// </summary>
        private GoogleTime()
        {
        }

        /// <summary>
        /// Create a time object from the given date time.
        /// </summary>
        /// <param name="dt">The date time.</param>
        /// <returns>The time object.</returns>
        public static GoogleTime FromDateTime(DateTime dt)
        {
            if (dt < zero)
            {
                throw new Exception("Invalid Google datetime.");
            }

            return new GoogleTime
            {
                TotalMilliseconds = (long)(dt - zero).TotalMilliseconds,
            };
        }

        /// <summary>
        /// Creates a time object from the given nanoseconds.
        /// </summary>
        /// <param name="nanoseconds">The ns.</param>
        /// <returns>The time object.</returns>
        public static GoogleTime FromNanoseconds(long? nanoseconds)
        {
            if (nanoseconds < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(nanoseconds), "Must be greater than 0.");
            }

            return new GoogleTime
            {
                TotalMilliseconds = (long)(nanoseconds.GetValueOrDefault(0) / 1000000)
            };
        }

        /// <summary>
        /// Gets the current time.
        /// </summary>
        /// <value>
        /// The current time.
        /// </value>
        public static GoogleTime Now
        {
            get { return FromDateTime(DateTime.Now); }
        }

        /// <summary>
        /// Adds the specified time span.
        /// </summary>
        /// <param name="timeSpan">The time span.</param>
        /// <returns></returns>
        public GoogleTime Add(TimeSpan timeSpan)
        {
            return new GoogleTime
            {
                TotalMilliseconds = this.TotalMilliseconds + (long)timeSpan.TotalMilliseconds
            };
        }

        /// <summary>
        /// Converts this instance into a <c>DateTime</c> object.
        /// </summary>
        /// <returns>The date time.</returns>
        public DateTime ToDateTime()
        {
            return zero.AddMilliseconds(this.TotalMilliseconds);
        }
    }
}

Final thoughts

Querying Google Fit using data sets, sources and types might seem difficult – but it is doable using Nuget and¬†the documentation. Hopefully they’ll make it easier.