The path to rejection in the apple App Store

I have been developing apps on iOS for quite a while now as a side hobby. The latest Xcode has an ever changing directory for the Xcode simulator, which has been a bit painful to work around, despite a few tricks. So I decided it was a good opportunity for me to get into OS X application development and build a little tool that would let me organise and access files on the simulator for each app work on. I had been always writing my user interfaces fully programmatically so I also decided I would take this opportunity to explore interface builder and storyboard.

The first impression of OS X was that it’s actually not that different, some classes have different names and APIs but it’s remarkably similar. I felt right at home using NSTable. I got the basic of the app working very quickly and it instantaneously proved quite useful to my app development workflow. So much so that decided to share it on the AppStore as a free utility for other developers.

So on I go, paid my $99 to apple to become a Mac registered developer, as until know I only was an ios developer.

Sandboxing

First hurdle was that I had to sandbox the application. This meant that access to the terminal via AppleScript stopped working, but most significantly it wasn’t possible to access the simulator directory as it was outside the sandbox. I used the terminal as I wanted the app to let you pop up a finder window or a terminal window on the directory of a selected app.

Not to worry, according to the sandbox guideline you can access any directory as long as the user grant access by showing intent: all I had to do was to bring up a open dialog window on the directory where the simulator reside and once the user pressed ok, the app had access.

For the terminal, once you figure the syntax you could explicitly ask for access to terminal in the entitlement file. Et voila, all the functionality was working again in a sandboxed app.

There was though one major issue. Every time you pop up the open dialog, it would pop up a new duplicate window. It was really annoying and drove me nuts for a while. Without sandboxing it was all working fine, as soon as you turned on sandboxing that duplicate window kept coming up. I searched in the development forums, documentation, etc. I didn’t find any solution or mention of that problem. So I filed a bug report (easy to reproduce the problem with a very simple project) and used one of my support tickets (you get two as a registered developer) to ask an apple engineer for help.

Almost two weeks later I got the answer from apple. It is a bug on apple side, to solve it I should not use storyboard… Great. So I refactored all my code to use an xib file instead of storyboard and now it worked!

First Submission

Feeling all set to go, I purchased an icon on shutterstock, quickly added some help and documentation to the app and submitted it to the Apple Store for review. One week later, I got my first rejection. I so far never received a rejection for my iOS app, so it was quite disappointing. The feedback was that I used entitlement that I shouldnt.

I figured this must have been the access to terminal. Even though it seemed to use the standard method to request entitlement, I had always been a bit worried in the back of my mind that accessing terminal could be a security issue because it would let you execute potentially damaging commands.

It made sense and I removed that functionality and resubmitted the app. One week later another rejection with the same reason. You can’t be serious! Actually it was: silly me I had resubmitted the app without selecting the new binary, so they reviewed the same app I had submitted the first time. Another week wasted.

Final Rejection

I carefully selected the updated binary and resubmitted the app.

Almost two weeks later the app goes into review. There are typically two stages in a review: waiting for review, that in my previous experience takes a few days to a week, then it becomes in review, which takes a few hours to a day or two. Nice, I felt hopefully it will go through soon…

Not so fast. A few days pass. Then a week. Still in review. I almost forgot about it when after over three weeks, I start to wonder. Did something happen? I did some research on the Internet, app review times gives some average days to review of 8 or 9 days. I am way beyond… After a month of being in review I write to apple asking if there is any information they need from me to review the app. The answer comes quickly: no information needed, they are still reviewing the app and just had to be patient.

After more than once month I just received the final rejection. The app is rejected because “I am modifying user file in a way that is not publicly documented”. I am not modifying any file in the app! But it then hits me: apple just does not want me to write this app.

I write a last appeal to the reviewer: I do not modifying any file in my app, only bring up a finder window on a directory after the user has granted access and showed intent by clicking Authorize on an open dialog box. The final answer from the reviewer: we just don’t want app to access simulator files.

Here we go. Hard to not feel a bit of frustration and disappointment, almost three months after starting the app. I still believe this app is really useful and became an important part of my debugging workflow. So I put a bit of wrap around and made it available to download via this website and hope others will find it useful as well.

Performance Analysis

In the version 1.20, ConnectStats supports a first version of long term (fitness) versus short term (fatigue) performance analysis. This is a bit rudimentary for now, and hopefully will improve over time.

The performance Index

The analysis is based on two fields, a summable field like distance, time or elevation gain and a second field to rescale it like heart rate, power, etc.

The analysis is based on an index built using this scalable field and summable field.

To access the analysis you need to select from the statistics field view, a field. If the field you select is summable (Distance, Time, Elevation Gain) it will use it as the summable field and choose Heart Rate as the scalable field. If you select a non summable field, it will use that as the scalable field and distance to sum.

Once the two fields are selected it will then apply a formula to get a performance index. The formula in this first version is simply to multiply the two fields, similar to a very simple TRIMP index, but in the future we could change that, for example along the line of normalised power and apply a function scaling more realistically to how the scalable field impact the distance field. This page gives some interesting comparison of the different way to do that.

Fitness (Long Term) versus fatigue (Short Term)

Given the two fields above and the performance index, then we will try to compare the long term accumulated fitness versus the short term training. We pick two periods, the short term period and the long term period, and plot the average performance index of the long term period versus the short term period.

Currently the short term period is the last seven days and the long term period is the month prior to that.

So the idea is to show how much training accumulated over a month (long term fitness) versus how much you are currently training. If your short term training is significantly above the long term fitness, you maybe over doing it. And you maybe taking it too easy or resting if the short term fitness is quite below the long term fitness.

In a future version I could parametrise both the performance index function and the periods used, depending how much people feel the idea is useful or not. So don’t hesitate to give feedback either with a review, tweet, comment or bug report.

Example

Once you selected a field in the statistics view, tap the bottom plot to iterate between the different choices: Monthly value, performance index graph and histogram/distribution of values.

Here is my current running performance. You can see in this graph that recently I have been training a bit more which raised my long term fitness, while the toward the end november I did less running which lowered the long term fitness .

Screen Shot 2016-02-24 at 05.20.48

New Statistics Plots

In the version 1.20, I added to the main statistics page small preview graphs embedded in the table. I also rationalised somewhat the plots shown on individual fields.

Main Statistics Table

The statistics page start looking like this

EmbeddedPlots

For selected fields, you now see a small preview of a relevant graphs.

Here in distance it shows you the cumulative distance of the previous years, one of my favorite graph to track how you are doing on a given year compare to the previous ones.

Note that you can disable the embedded graphs with an option in settings in case you don’t like it.

For the Average Heart Rate and other non summable fields, it will show you the monthly average over the last 6 months.

Pressing the All button on the right will continue to rotate between the weekly, monthly and annual summary. The Sigma icon means it displays the total or average across all activity. If you press it, it will display the stats restricted to either the last week or last month. This enables you to see all details of the last month or week.

WeeklyStats

Here you can see that the Max Heart Rate over last week was 194, average moving pace 5:21 min/km. This enables you to see any statistics over that period easily. The weekly summary of the previous versions was limited to only a few key measures. Note that in this view the embedded plot becomes a weekly plot to compare this week’s statistics to the previous.

Field Statistics Details

If you press any field of the main statistics table, it will take you a more detail information on that fields, as for example here

StatsMonthly

This shows you two graphs and some basics stats. The first graph is a scatter plot against another variable. If you tap on that plot it will let you configure it and choose the second variable.

The bottom plot will rotate when you tap on it between a monthly summary, the performance analysis graph and an histogram of the different values as here. This post describes the performance analysis in more details.

StatsHistogram

Pressing the all button on the top right as before shows you weekly or monthly statistics.

MonthlyStatsDetails

Garmin automatic synchronisation to Strava and implication for ConnectStats

Strava now automatically synchronises with Garmin

Garmin provide now automatic synchronisation with different services, most notably Strava. You can read about it from this post from @dcrainmakerblog.

I find it very useful. I personally use Strava as well, I like their segment functionality and the social aspect of followers.

This is a continuation of the new policy change of Garmin to not support 3rd party app that access the data from garmin directly. You can also read about it here from @dcrainmakerblog

What’s next for ConnectStats

For ConnectStats, this new approach of auto sync just does not work. I do not have the ability, the means nor the time to build my own service to save users data on a web server and use that from ConnectStats.

Garmin announced that they would stop supporting the API ConnectStats uses, but as of beginning of August and the time of writing this, it still continues to work. We just don’t know for how much longer.

To address the potential shutdown, ConnectStats can use Strava as data source instead of Garmin Connect.

I still believe that ConnectStats can be quite useful to many users with extra plots, reports and views it provide currently not available directly from other services on an iOS device. So I will try to continue maintaining ConnectStats even if Garmin shuts down the API access. For that purpose the auto sync to strava is very useful, because it lets ConnectStats use Strava as a service provider to replace Garmin Connect.

What ConnectStats users will loose

It’s not all perfect though, here are some of what will be lost when Garmin Connect shuts down its service and ConnectStats user have to switch fully to Strava:

  • The auto sync service only seems to upload recent activities, long history of data will no longer be available to users.
  • Strava API itself only lets you download the two most recent months of activity via their API. This is quite an issue because a large part of the attraction of ConnectStats is to do comparison and plots that go back in time. My favourite features are to compare my fitness evolution over long period of time
  • Strava API currently will not provide access to all the different fields provided by garmin devices. Training Effects, Normalized Power, Vertical Oscillation, and many other data will no longer be available access to the garmin service stops and ConnectStats user switch to Strava.

Note that to mitigate the issue with only accessing recent history from Strava, I plan to add a merge feature to ConnectStats multiple service support, so at the time Garmin Connect API shuts down for those lucky user that already have their history saved on their device they can continue and use strava only for new activities.

Recent Silence

I have been recently a bit quiet. I admit the idea of potentially not being able to download data from Garmin and the implication of an end of ConnectStats wasn’t a great motivation to build new features.

Recently though I had a couple of ideas that excited me. I am still working on ability to keep track of time in zone, best rolling plots, Critical power plots etc over time. I would love to be able to compare my best rolling plot of current month versus last, or versus a given year, etc.

The other feature I am working on is ability to compare your recent performance (current training) versus your long term built fitness. Somewhat inspired by training peaks’ performance chart, and leveraging ideas from the following articles about how to measure impact of exercise.

Improved Statistics Page

Version 1.19 includes an improved and better organised statistics page.

The statistics page is quite simple to find on the iPhone. A few users on iPad sometimes miss it as it’s a bit less obvious, you need to press the stats icon pointed by the blue arrow below.

IpadStatsIcon

The New Fields Stats Page

The new stats page now have the fields pre-organised rather than trying to dynamically work out the ordering based on all the fields found on garmin connect data. This is both so that it works better with other services than garmin and due to the fact that the data include now a lot of new fields that confuses the old logic resulting in quite a messy page. Note that any fields not known by the app will still appear systematically at the very end of the list under the category Other.

NewStatsPage

One new feature is that you can now have a quick filter to see the summary stats for either the week or the month of the most recent activity. For that press the Sum Icon, which will then turn into a week or month icon as pointed by the arrow below.

Screen Shot 2014 04 12 at 11 38 58

Weekly or Monthly Summary

As before if you press the All button on the right it rotates through weekly, monthly or yearly summary. There is now also a quick filter for these pages. If you press the All button pointed by the arrow below you’ll enter to filtered page for the last 3m, 6m or year.

WeeklySummaryAll2TimeFilter

Note that if you want to get more detail on an activity you can now tap on the line for the period you are interested in and it will bring up the page with that full stats on the activities. Below you will see all the statistics for the week starting on the 23rd of march.

WeeklySummaryFor3m

It achieves that with the search feature, the activity list will also contains only the relevant activities. You can from the activity clear the search to see the full list of activities again. When the stats are about the current search it then replace the activity type icon. Note that you can this way get stats on any subset of activity you can define with a search in the activity list screen.

WeeklySummaryTapToDetail

Another subtle change in the Garmin API

UPDATE: thanks to a tip from @GViewerPro I have now a fix which will be a next release. Thanks a lot.

Since the login change, another less obvious change to the data service of garmin has resulted in a few issues with ConnectStats.

The track data contains now significantly less points. It used to provide points as often as recorded, but now the track data service returns a lot less points.

Here is an example of an activity downloaded before and after the service change. Notice the scatter point contains a lot less points.

BeforeMorePoints

AfterLessPoints

This has a few consequences in the app:

Smoothing

The app smoothing logic is not working as well anymore. We now have a point every 10 to 20 seconds, so it’s harder to figure out how to smooth. Intervals for example may not be plotted properly if they are less than a minute. If that happens turn off the smoothing, by selecting the graph full screen and pressing the Slider Icon to show the smoothing options.

Smoothingoptions

Auto Lap

The logic in auto lap was trying to account for times where the device was on pause. This logic gets mixed up with the new sparse points. A fix and a better logic is on the way for next update.

What I will do about it

I have somewhat tweaked the logic in the smoothing and auto lap feature and this will be incorporated in the new release 1.18. I am also investigating to use the tcx file available. This file seems to still contains all the data point. ConnectStats used to get its data from that file, but it was changed to a different api that contains more information, like the running dynamics of the new ForeRunner 620. I will probably see if I can do a merge of the files, use the tcx file for main data and the sparse data for running dynamics and other fields.

Multiple Profiles in ConnectStats

ConnectStats allows you to maintain multiple profiles. This can be useful to either track several users or now several services independently.

You access the profile setup from the settings view, the blue arrow 2 below

ServiceAndProfiles

You will then see the list of profiles existing in the app. It will start with only one named Default. Each profile will maintain its own list of activity and service setup.

You create a new profile by using the New Profile line. It will the prompt you for the name of the profile. Please avoid using special characters in the name. You can then simply tap on the profile you want to activate at any time.

Here is the list of profile in my current setup. You can see I created profile for different service setup and other accounts in garmin connect I use, as well as for the activity of friends.

Screenshot 29 03 2014 10 14

If you use the Delete Activities or Profile, this will let you either completely delete the current profile or some activity in the profile. Note that deleting activities here, is only locally in the app, not on the remote service, at the next refresh the activity will be downloaded again. It’s mostly useful if you want to make sure you refresh many activities from the remote services at once.

View or manage cache is simply a way to see how much disk space the app uses across the different files it keeps.

ConnectStats setup for multiple services

From version 1.8, ConnectStats can source its activities data from different services. This was mainly done such that there is a future for the app, even if Garmin decide to close its data to 3rd parties.

In the first release, ConnectStats will support extracting data from Strava. At this point, getting the data from Garmin is still the only way to get running dynamics data for the new garmin devices (Forerunner 620), or some advance statistics like Normalized Power, etc. But Strava provide a good alternative for everything else.

We will go over in this post how to set up services. Please note it can be quite useful to maintain different profiles when using multiple services. You can also have several services enabled for one profile and it will combine the activities.

You access the setup from the settings page as below. The green arrow 1 points to the services setup, and 2 to the profiles setup.
To enable a service for activities download, turn on the Download Activities toggle.

ServiceAndProfiles

Services Setup

The service setup screen will initial shows the list of service with the setup for Garmin expanded. After all, at this point this app is still focused on the Garmin service… Simply tap on the service name, pointed with the blue arrow, to expand or collapse the setup information for a given service.

ServiceDefault

Garmin Setup

For garmin, once you enable the service, your main choice is the log in method.

  • With the direct method you enter your name and password, and ConnectStats will handle the login directly.
  • With the Web method you need to enter your name and password manually when required in the Garmin login screen. The direct method is much better, but unfortunately the Garmin service seems a bit capricious and sometimes returns an Access Denied, in which car using the web method can usually help.

Note that from the experience of users so far the Access Denied error usually is transient and after a day or so the connection may work again. Needless to say that Garmin has so far not helped providing any info how to avoid the problem. If they charge the fee they mentioned, one can only hope they will provide better support and a more reliable 3rd party access process.

There is also a Manual Login option. This is a last resort tool if all fails to see via a browser what happens during the login process. Try to login and once the Garmin Connect Dashboard appear, you can press either the List button or One to manually query some of the API the app is using.

Strava Setup

You can use Strava service in two ways:

  • As the source for your activities, when the Download Activities toggle is on.
  • To upload the activities you look at, when the Auto Sync on Download toggle is on.

There is no entry for the username and password in Strava, as the service rely on authentication via Strava website directly. What that mean is that, when you will try to use the service, the Strava website login will pop up. You will then need to enter your credential and the first time, authorise ConnectStats to access your data. Once you have connected once, usually the login won’t be presented again, unless you change profile.

Withings Setup

You can use Withings data to enrich your analysis with history of your weight or other measurement from a Withings scale.
To Enable, you need to enter your name and password in the setup, and Tap the Press to login line.

If your credentials were entered correctly, the screen should show the following

WithingsOk

If you have several user in your Withings accounts, you can select the correct one to use.

If you select the Auto Refresh option, every time you update your activities, ConnectStats will query the Withings service for new measures.

When the service is enabled, you will see in each activity details the latest weight corresponding to a given activity and in the Stats section you’ll be able to use the weight series.

Garmin Connect New Policy Impact on ConnectStats and 3rd party apps

First I want to be clear that I have been a huge fan of Garmin’s for years. I own 6 devices and think they are extremely well done. I have also encouraged a lot of friends, colleagues and family to purchase Garmin devices over the years.

Monday March 2nd, Garmin announced they would stop making their API freely accessible and that 3rd party would have to pay $5,000 to access the data in Garmin connect.

I understand their infrastructure is costly to maintain but I feel such a steep fee risk killing the independent 3rd party applications which I believe adds value to Garmin customers and therefore Garmin as well.

I would propose a multi-tier system where 3rd party apps register and get a first limit of daily access for free, say 50,000 access per day. Heavy user breaching this limit would have to pay the 5,000$ fee.

I believe this system would be to both Garmin’s and its users benefit.

The Policy Change

On wednesday February 19th, Garmin updated its website which resulted in the approach used by most apps and website to access garmin connect data via an API to fail. The api used to have a license saying the following


This is the license file for GarminDeveloper.com. You are free
to access our API as long as you agree to create great things.

The link to that license has now been disabled http://connect.garmin.com/proxy/activity-search-service-1.2/downloads.html#License

On monday March 2nd, Garmin has notified developers that they are changing their policy and will charge $5,000 dollar fee for access to the API. Here is an extract of the message about the new policy.

Garmin has instituted a new policy regarding the accessibility of our developer programs. The previous strategy of freely available APIs quickly became unsustainable due to increasingly high demand. In response, we have established a new pathway for our Connect API. Firstly, there is a vetting process. We are strategically limiting the scope of this program to specific developers that will enhance the user experience. Secondly, those who are approved for inclusion will be charged one-time $5,000 administrative fee to cover the extensive engineering and server support required for the Connect program.

Consequence of the decision

This decision will likely force the eco system of independently developed 3rd party app to partially or completely disappear. This is a consequence which will affect customers. A lot of innovation can come from independent developers. Niche functionality can make its way to customers. It is likely not worth Garmin’s engineers’ time to cater to all customers software needs or custom data analysis needs. Enabling third party to provide for them helps make the Garmin devices more attractive. The size of the fee will dissuade a lot of experimentation for small players. The cost of entry is too high.

Most disturbingly it means it prevents people to use their own data in more creative ways than just using the data from the website. ConnectStats started as purely a tool for myself to look and slice my own data. I decided to share it as it could be useful to others, but right now it means I would not be able to use my own data in my code without paying $5,000, even if I pull the app from the store. This sounds wrong to me. Savvy users wanting to use a script to import their data efficiently for processing won’t be able to do that either.

In a world where the fitness market is exploding, I feel it would be to Garmin’s and its customers’ interest not to exclude this source of innovation for functionality.

My choices

I have the following choices for ConnectStats:

  1. kill the app. It would really pain me as part of the initial motivation was to provide a service for myself I couldn’t otherwise get: a more advanced viewer for my data on iPhone and iPad that what Garmin offers.
  2. swallow the cost. It’s a lot of money. I intentionally kept the purchase price at the minimum as to cover development cost and devices (money going back to Garmin by the way…) but my key motivation beyond my own use was sharing the app and not really generating large profits.
  3. increase the sell price of the app and hope people will continue to buy it. It somehow feels wrong to charge the users more just for the sake of them accessing the data they should own already. Right now if I increase the price from $.99 to $2.99 for ConnectStats, I am hoping to recover the Garmin fee over time. People pay several hundred dollars for their device. I hope they will accept the few extra dollars to pay Garmin for the right to access their data.
  4. switch ConnectStats to rely on another service. I am already working on that to use strava as primary download service and hope strava will pay the fee to get the data from garmin. This has some downside as some data won’t be available and Strava does not support swimming and skiing which I personally rely on.

Comparison to others services

I also wanted to comment on the fee structure proposed. A fee of $5000 is a lot of money by any standard. Niche independent Apps do not generate much revenue, mostly cover the costs. Here are some other services I use as comparison.

  • Apple charges $99 a year plus 30% of every sale to access their development tools and app distribution infrastructure. And this infrastructure and marketing power is of extremely high quality and adds a lot of value to independent developers like myself and to its customer. It wouldn’t occur to me to complain about this cost.
  • Strava provide an access to its data for free up to a certain rate limit once you are registered. Their API is extremely well documented, modern and their support and help to my questions have been great. I applaud Strava would provide a sophisticated website and API for free when their business model does not include revenue from any hardware.
  • Garmin support and documentation was so far inexistent. There are no or little documentation. I have never received an answer to any of my inquiries. Everything had to be reverse engineered. It doesn’t bother me at all given it was free but for $5,000 it’s a different issue.

I have been inquiring with Garmin about what exactly is provided when I pay the fee. What exactly do I get for that money: same API? Do I need to adapt my code to a new API? Do I get more documentation? Better support? Some marketing/promotion of the app as Apple provides?

Proposal

I believe the independent and small 3rd party partners add a lot of value to Garmin and the ability for users to access freely their own data is important. I understand the potential infrastructure cost and need to control usage for Garmin.

I suggest they offer a tiered system. Any user of the API would have to register and get some basic number of API access per day, say 50,000. Users above that limit would be required to pay the $5,000 fee to use the service. This would allow them to recoup some infrastructure cost from heavy user but still allow their customers to benefit from 3rd party functionality and access to their data.

What I plan to do for ConnectStats

I am doing 2 things:

  1. I have written to Garmin to explain why I feel a tier system would be better for all parties involved. I hope they will agree.
  2. ConnectStats is somewhat successful, receives good reviews and regular download. I hope by raising the price by an additional $2 this should help sponsor and recover the fee from Garmin. I have applied to be part of the program. Assuming Garmin approves my application, I plan to pay the fee myself and hope people will continue buying the app at the higher price to recover the money over time

Thanks for reading.

Comparing activities in ConnectStats

My activities are often on the same paths, so I wanted a way to compare activities. Of course segment in Strava are really useful in finding out how fast you can run or bike certain route, but I wanted a bit more control in what I was comparing. I also wanted a way that is not over complicated within the UI flow of connectstats. Here is what I came up with.

To compare an activity you need to first mark the activity that will compared to any other activity you will then look at. For that, you slide an activity in the list to the left, which reveals the Mark button.

Markactivity 6

After you press the button and the activity is then marked, note the mark icon on top of the activity icon.

Markedactivity 4

You can then look at any other activity and the graphs of the marked activity will then appear in the background of the current activity. Here I am looking at activities where I skied up the same slope with skins. It’s clear the first one my heart rate was much higher than the compared one. It was pretty much the same path up, so really just show how I adapted to the altitude over the course of the week, I think… Look at the HR histogram

SkiupHRHistogram 3

Or the rolling best plot

Skiupbestrolling 3

Now of course I claim I took the same path up, or did I? Hit the map and the two paths will appear. The compared path is slightly less opaque

Comparepaths 2

Here you see though large part of the ski up was the same, I didn’t start from the same exact same place, but arrived at the same destination.