Preface

Many Android developers were once web developers in a previous life. If you are a web developer making the shift to Android, one thing you may have never considered before is upgrade paths.

For web development, you often don’t need to take this into consideration since users will always be seeing the latest production code serving your website. The same is not true for mobile apps. Once you release a new APK into the wild there’s no guarantee your users will upgrade to, or later from, that version. Therefore, you should always take backwards compatibility into consideration. Not doing so is an easy way to have a loyal user later uninstall your app.

As a user

From my experience here are a few reasons Android users may not upgrade:

  1. Simply lazy to do so. There’s not much you can do about this.
  2. Annoyed by frequent updates, so they skip a few. Releasing updates too often with only minor changes can make users more reluctant to upgrade.
  3. Doesn’t like new app permissions if any were added. After the recent Google Play upgrade I’ve noticed more complaints about permissions. Users now are generally more conscious about what apps do on their phone.
  4. Prefers their current version. Why change if they’re happy?
  5. Knows that a new version of the app will remove a feature they enjoy.

As a developer

Unfortunately, this means a few things:

  1. Never remove old endpoints unless you are sure you can migrate everything over. Old versions of the app might still try to talk to the old endpoint.
  2. Previously persisted objects could become incompatible in new versions, leading to a serialization problem.
  3. Upgrades are not necessarily consecutive. It’s perfectly fine to upgrade A -> B -> C or A -> C.

The big thing to remember here is that a user won’t always be upgrading from the previous version. You might test your new version against the last version and not consider a breaking change from several versions ago. Obviously it’s not practical to test against every possible upgrade path, just keep this in mind when you are trying to debug the seemingly impossible stacktrace.

Mitigation strategies

By following some best practices you can eliminate most headaches involved when dealing with large amounts of upgrade paths.

  1. Version your API. New clients will use new versions of the endpoint, old versions will continue to use what they have. This way you’re not trying to make something work for everything.
  2. Allow for dynamic configuration. This could be a configuration file that your app downloads every now and then. This can help future-proof parts of your app without requiring the user to upgrade.
  3. Document every persistent object. This includes Shared Preferences. Even though it’s tedious, it will be much easier to keep track of potential upgrade problems.
  4. Include a kill-switch in your app.

Killing old versions

My last point is worth elaborating. Every large and established app should have a kill-switch built into it. Periodically check with your server to see if the user’s app version is valid, and if it’s not then remove all functionality in the app and display a dialog to upgrade.

Why is this important? Well, first is security. You could detect a serious vulnerability in an old version of your app and need users to immediately upgrade.

Spotify recently had to do this exact thing, except it was even more serious in their case. Spotify had an issue — not disclosed specifically AFAIK — where they had to issue a completely new keystore file to sign their app. Not only did they need users to upgrade, they needed to download a completely different app! They achieved this by locking all functionality in the old app and displaying a dialog with a link to the new app.

Aside from security, at some point you will want to retire old APIs. The engineering effort to maintain old code will eventually reach the tipping point.