QuickFit 1.1 released

Juliane Lehmann / Mon, Apr 11, 2016

QuickFit now allow scheduling workouts on a weekly repeating basis!

This update is all about taking even more thought and effort out of tracking workouts with Google Fit: Set up once when you plan doing your workouts, and then get reminded via a notification to actually do it, and to enter your session into Google Fit. The focus is still on delivering minimal UI that allows the user to get all needed information as fast as possible.

Again, my goal here was to up my Android game, and I learned a lot. First of all, organizing the data model needed some thought: The workout list activity should show all info about the individual workouts packaged in a list item each, including full info on the schedule. That activity depends on using a CursorLoader/ContentProvider combo, relying on the observation of the content provider by the cursor to update the UI on content changes. Content Provider data are inherently accessed hierarchically, supported by the notification URI, but the data itself is represented in Cursor form, so very much not hierachically organized the way for example Json data could be. This mismatch caused me much grief; ultimately I settled on a solution with a lot of logic in the ContentProvider. The URI scheme for accessing workouts and their associated schedules is workouts/{workoutId}/schedules/{scheduleId} or any prefix thereof. When querying against this URI, the result set depends on the requested projection columns: If the request can be satisfied by accessing only one of the two underlying tables, the corresponding rowset is returned straight. If joining is necessary, the returned rowset will represent a left outer join of workouts against schedules, so consuming code must deal with the repeated root entities (workouts, in this case) and the possibly null columns on the schedule side.

Much fun was had presenting a week starting with the correct day according to the current locale. After some experimentation, I gave up on trying to get the AppBarLayout in the main activity to behave precisely how I wanted - ideally, the RecyclerView would have layout_height="wrap_content" (possible since support library 23.2.1!), which automatially makes no scroll events happen if there are not enough items in the list to necessitate it. But then, by deleting one item in the right circumstances, the app bar can get stuck in collapsed position. But some pretty material design UI goodies did make it: there are leave-behinds (completely generic and reusable! See the class itself, and how it’s used.), and pretty CollapsingToolbarLayout, without image, and item dividers that follow the items when swiping.

There’s a (not generic) SortedList replacement, that diffs itself against updates and emits the appropriate change events for the RecyclerViewAdapter like a SortedList, but does not conflate the questions “When are two items identical?” and “In what order should items be sorted?”. To achieve this, it is less performance-optimized than SortedList, but really, who plans the same workout for a thousand times per week? Performance is absolutely fine with the at most 10 items I’d expect there.

And then, of course, there is the actual alarm setting and notifications. I wanted notifications, snoozed ones too, to be persistent over reboots (just because you let your phone starve shouldn’t mean it will just conveniently forget to nag you about doing some sports), and I made it so. I learned about wakelocks, I learned about the limitations of alarms in Doze mode on Marshmallow, and once again build notifications, with all the bangs and whistles! OK, not all of them; I kept bridging to Wear devices for the next release. I played the PreferenceActivity game to my satisfaction.

Finally, I was too lazy to take all-new screenshots in 2 different locales on 3 different form factors, so I automated the process, but I’ll write more about that in another post.

QuickFit is free on Google Play, or be a beta tester

Find the source (Apache License 2.0) on GitHub

I’d be happy about feedback on reddit, or raise an issue on GitHub.