On one hand — you work fast, release frequent updates and push your product forward.
On the other hand — your customers are running behind. They are not automatically upgrading their apps, forcing you to continue and support old versions of your app.
I assume most of you, tech readers, either use the automatic app update option or review your apps in the AppStore every once in a while.
This is not the case with many of your non-technical users. I recently discovered my wife has 45 updates waiting for her, and I’m not even sure she has 45 apps installed on her phone…
Non-technical people often have the ‘automatic update’ turned off
As we are witnessing at Missbeez: monitoring our active customers traffic shows that 2 weeks AFTER releasing a new version of our app — over 80% of our active customers were still using older versions.
That’s more than 80% not taking advantage of our new features and more importantly: 80% that are not aligned with our latest business logic and new behavior.
I call this phenomena the long tail of mobile apps, and it’s quite unique to native apps because web apps are usually getting their latest sources automatically when the web page is refreshed.
The long tail of mobile apps causes a headache to the product & development team because every new feature needs to be designed in a way that will not break old versions of the app. In many workflows of your app this might work without doing anything special, but the challenge lies within sensitive workflows such as check outs, payments, discounts, etc. where the same behavior must apply to all the users, regardless to their version number.
There are few design principles to better control your old versions and maintain a consistent business logic without forcing your users to upgrade. Here’s a short overview (and I’m pretty sure there are more):
1. Load all you app resources from the server
Store your app resources (strings, images, sounds, etc.) on your server and download them to your app when needed.
This will allow you to modify much of your product UI without having to release a new version for every change.
It will also give you a direct control on the look & feel of your older apps and a pretty nice way to A/B test visuals and micro copy.
You can minimize the impact on performance by developing a “smart” download mechanism that will only run when something has changed.
2. Use dictionary objects
This is a common pattern in B2B products where configurability is key: replace your code enums with a dictionary collection stored in your database.
This will allow you to change your enums on-the-fly without having to change the code.
For example, if a certain action requires selecting a “reason-code” — it’s better to store the reason codes in a table.
Doing so will ensure all the components of your product can access those enums, and as your product evolves, you will be able to change that list without worrying about the users using your older releases.
3. On/Off switches
When you develop a B2C app you cannot always predict the impact of new features on your customers behavior.
As a backup, you should always include on/off switches for your features to allow an easy rollback if needed.
Having a switch allows you to safely try new stuff and quickly revert back if the results are not what you’ve expected.
4. Move logic to the server
As a rule of thumb, I recommend moving all of your business logic to the server. In fact, any piece of logic that might change in the future should reside on the server side to allow maximum flexibility, even if it is a client side feature.
It sounds weird at first, especially since native apps are often developed as “fat” clients, but believe me — stay away from client side logic that will eliminate your flexibility and complicate your backward compatibility support.
Do it all on the server and when the day comes to change something (and it will come), you will be able to change it in one centralized location and it will immediately change the behavior of all your installed apps.
Let’s say you are selling services through your app with coupons, discounts and all the usual goodies.
Now, let’s assume that at first you only have one simple type of coupons with fixed price. That’s probably a very simple calculation to do and you will probably develop it in the client side: if a customer needs to pay $100 and uses a $20 coupon than the total amount to pay would be $80. The cart should show the total items, the discount and the total sum to pay. Doesn’t sound like something that needs to be calculated on the server, right?
But now, let’s assume that you start distributing a new type of coupons with % discount.
Assuming many of your customers are still using old versions — they won’t be able to use those new coupons since their app will not know how to translate those new coupons into discounts. Users with old versions might see a fixed price of the discount while the actual one will be calculated as a percentage of the total deal.
The solution is to manage the whole payment logic in the server side (including even the code that builds the cart summary!). This way you have one centralized place to modify your logic and older apps will still be aligned with the new functionality. And keep in mind this is a very basic example and the real-world examples are usually far more complicated than that.
5. Backward compatibility
Moving your logic to the servers makes sense, but you still need a structured way to support your old apps.
The server needs to know how to handle each request, because sometimes the version of the app dictates slightly different response.
A good practice would be to include some meta data in every API call; something like the OS type, the app version and the user ID (and whatever makes sense for your product).
The server gets the request with the meta data and if needed — can split the function logic into relevant chunks as required by the business, or as required to support older versions of the app.
6. Force upgrade
There are times when the above techniques will not work or will not make sense (effort wise). In such cases you better have an option to force your users to upgrade the app.
The easiest way is to have a simple API method that is being called every time the app starts. The client reports his version number to the server and if it’s below a certain number (meaning it’s too old) — the server returns an “upgrade required” error which is presented to the user blocking him from continuing until he clicks on the “upgrade now” button.
This option will make your developers very happy because it eliminates the need for backward compatibility, but on the flip side — it might frustrate your users and cause a certain churn (some users will probably not follow your upgrade request and simply quit).
It’s important to have this capability in your app from an early stage (because in early stages you make drastic changes and definitely don’t want to waste time on backward compatibility), but you should minimize the use of it because of the bad user experience.
7. Generic error codes
I haven’t seen this option being used a lot, but I think it’s a nice one: whenever you make a change to one of your workflows — you can use your server to block users with old versions from performing certain actions.
Let’s say that your app supports 3 main workflows and one of them was completely modified in the latest versions. You can then use the API meta data to block users with old versions from performing this action by returning a generic error code to the client code.
It means that key workflows in your client side code should have a proper error handling that knows how to parse this generic error code and tell something like: “this action cannot be done because you are using an old version of the app… please upgrade to get the most out of…”, and include an “upgrade now” button below the message.
Having the above alternatives mean your product managers and developers have enough tools to tackle the long tail of mobile apps by deploying the right solution for each case.
Originally written for The Mobile Spoon
Follow me on twitter!