Tuesday, November 08, 2016

Building Shared Checklists (Part 1): The Android App

After determining that there's no app that did what I wanted, I decided to create Shared Checklists, which was an Android app that does do what I want.

I started with an Android app, since I did some development for my wife's app a year or so ago. The requisite tools are all free: Android Studio, my Windows PC, and my already registered custom domain. At first, I thought that I would have to write all the cloud-syncing code, but this being 2016, it turned out that I didn't have to: Firebase (acquired by Google in 2014) did all this fancy cloud-synchronization.

This is ironic, because as a back-end guy, the development of a cloud-database with syncing capability would have been fun for me, as opposed to the mostly UI-oriented development that an Android app would turn out to be.

Firebase's real-time database is a JSON-tree in-cloud database that lets you get at any subtree as a Java object inside an Android app, or treat each leaf-node as a subfield at whatever granularity you wanted. This is very nice: you get to do a write() using a Java object and have the serialization all done for you.

The penalty to this is that the Firebase API is a callback-oriented API. What this means is that updates coming in from the cloud can happen at any time, and you need to update the App's UI to reflect that. If not coded carefully, this can turn into a mess of synchronization bugs and crashes (not a surprise, right)? Even when coded correctly, you could spend a lot of time working through UI glitches, etc.

I started with a basic simple approach: a sequence of Android activities (screens) in which you deal with one aspect of the real-time database at a time: there'd be a checklist management screen, and a checklist screen. The checklist screen would let you add and remove items from a given checklist, while the checklist management screen would handle adding new checklists, sharing new checklists, and deleting checklists.

This was all pretty good: I eventually figured out that the Android App UI needed to be a dumb reflection of whatever the database was seeing, and rather than screwing around with the UI's model whenever the user made an edit or a change, I would just reflect the change in the real-time database in the cloud, and let the inevitable notifications filter down and then refresh the UI based on the cloud-activated events.

One surprising pain point was integration with cloud single-signon services. I don't know about you, but I get very annoyed now with apps and/or websites that force me to create an account. Don't make me create yet one more (likely insecure) password for your website. Make use of Facebook login or Google login services.

Facebook login was surprisingly easy. I basically got it working on the first try, which was pretty awesome. Again, it's a callback-oriented API, so I had to jump through hoops and play all sorts of tricks to get the UI to force the user to login first before I could show the contents. And that was OK. One major pain point is the integration between a database-oriented app and Android's concepts of activity screens and restore events. The documentation is horrible, and when restored from a stopped state, there's every chance that the real-time database connection had been severed! My quick and dirty solution was to basically force a Firebase reconnect and authentication every time the app was resumed from a stopped state. It's not very satisfying, but from a development point of view it was the easiest way to prevent crashes. Android studio has no way of simulating this sort of event, so it was a major pain doing it manually, fixing the crashes, and then attempting to get the OS to evict the process again. You would think that Android Studio would have a means of injecting such events into your app for testing purposes, but no.

Google sign-in integration, however, was a nightmare. Superficially, the steps seem similar. In reality, what you have to do is to run a backend server somewhere, serving a particular JSON file with certain values. You'd think that Google would automate this for someone who was running an app-engine backend on a Google hosted custom domain, but no dice. It could never get it to work! I eventually gave up completely and stuck with just Facebook login. This, by the way, now explains to me why you see Facebook login everywhere, while non-Google sites don't tend to provide Google login: the process is sufficiently onerous that you can't expect the typical web-site maintainer to use it.

After using the app for a couple of days, I grew unsatisfied with the UI. Upon reflection, I realized that the Navigation Drawer approach was the correct UI for the app. This effectively required a rewrite for the entire app to use the new approach. It was only a little painful: every Activity had to be turned into a Fragment, and then I had to rejigger some of the database schema to reflect the new approach. This approach also eliminated an entire class of bugs related to having a default checklist, so I was relatively happy with the end result.

In the end, the experience was surprisingly good. While Android Studio's layout tool still leaves much to be desired (and to be honest, for it to be a useful tool would require some sort of standardization of device that's pretty much impossible in the Android universe), it was usable most of the time. The compilation time had now sped up to the point where a full signed APK build took only 35s or so, while incremental debug builds during the crucial edit/compile/debug cycle was now under 3s. In practice, that was fast enough that the transfer to my debug device over USB was now as much of a bottleneck as build time.

All in all, given that the entire tool chain was free, I thought this was relatively satisfying as a hobbyist project.
Post a Comment