Blog

Photo credit: jjmusgrove, Flickr
Industry News

10 principles of good mobile library design

by Basil Shikin on Jun 19, 2013

As mobile development grows bigger more libraries are created. Utility, payment, ad network, social media and many other libraries are available for app developers to use. It’s not easy to create a well-designed, consistent and easy to use library. This article suggests a few practices that help guide a library vendor to a better product.

A change to a mobile app might take up to a week to get adopted, so the library vendor has to deliver a product of excellent design and quality.  The cost of a mistake is high and will be paid by the library’s users.

I’ve been building and using various mobile libraries for the last three years. During this time I’ve devised a set of principles that help to create good libraries.

1. Keep Basic Functions Simple

A good question that a library vendor should ask is: How many lines does it take to perform a basic function? If the answer is more than three, it might be too complex.

Three lines is a good rule of thumb, because first line is usually an initialization, second is some sort of configuration and third line actually performs desired function. To summarize, if you build an API for a puppy shop:

Shop shop = new Shop();
shop.setOpen(true);
Puppy newPuppy = shop.buyPuppy();

is simpler than:

VendorCreatorsFactory factory = new VendorCreatorsFactory(“/etc/vendors/vendor_config.xml”);
for (VendorCreator creator : factory.getVendorCreators())
{

if (creator.getSupportedProducts().contains(“puppy”))
{

Vendor vendor = creator.createVendor();
vendor.changeState(VendorStates.OPEN, true);

VendorTransaction transaction = new VendorTransaction(VendorTransactionType.BUY, “puppy”);
VendorTransactionResult result = vendor.submitTransaction(transaction);
Puppy newPuppy = (Puppy)result.getPurchasedCreature();

break;

}

}

2. Don’t Change APIs

The mobile world is dynamic, new features are released almost every month and old features are deprecated by the dozens. Still, a well-designed API should be a conservative product.

It is tempting for developer to change all function names so that they conform to a new, more consistent naming convention. But if there are people who are already using them it might turn out to be an inconvenience for them to learn the new way and refactor all of their integration code. A cleaner approach might be to introduce a new namespace (or package name, or prefix). This will allow the user to gradually migrate to the new APIs.

Changing the contract of the function is even worse. Serious bugs can occur when the function’s name remains same, but what it actually does changes. Library vendors do not know what assumptions the users made. Therefore, the library’s function cannot change any behaviors after they were declared.

Finally, adding new API functions sounds like a fairly straightforward change. Still, one has to be vigilant to make it absolutely clear why the new function is different from the old. Most of it is just selecting proper name, and not doing:

shop.buyPuppy();    // Old function
shop.newBuyPuppy(); // New function, why is it different?

3. Blend Into the OS

The mobile world is diverse. Libraries need to account for differences across platforms in a consistent way.  This often requires planning. For example,

user.getName()

looks very reasonable in Java, but

[user getName]

in Objective C contradicts the language’s conventions.

It is a good idea read best practice guides and dive into some open source code before porting an API to a platform. Using the right conventions, terminology and grammar is essential to making the library intuitive.

4. Fail Fast

Mobile apps are hard to test. Since there is a variety of OS and hardware versions, it is hard to know what could go wrong on a given handset. What is more, deploying a new app version might take weeks. Therefore a lot more emphasis is put on testing before releases.

Failing fast is a strategy that should allow the user and his QA team to find most integration issues at the time of testing. A user might be frustrated if a library swallows all errors during integration and testing phases, but then crashes an app when released into the wild.

Failing fast usually means re-throwing exceptions (or other issues) as well as constant checks for state consistency and valid function arguments.

5. Build on Advanced Functions

A well-designed API not only makes basic operations easy to perform, it also empowers advanced users who want higher degree of control. Although this might require reading the documentation or diving into the examples, advanced users should be able to tweak and configure the library to suit their needs.

I’ve found that the best way to give advanced users the most control while keeping things easy for the regular users is to design a “simple” API as a wrapper of a more full-featured set of functions. So, a user has a choice of either going with the default behavior or diving into the more complicated functionality.

One of the ways to enforce such library design is two have two people work on the code: one works on the full-featured public API and another engineer works on the “simple” public API. The engineer working on the latter can only use the full-featured public API and is prohibited from modifying any private library functions.

6. Use the Lowest Common Denominator 

Do not introduce essential features that rely on unadopted OS or hardware versions.

Sometimes library features rely heavily on some specific OS or hardware versions. In these cases it should be obvious to the user that the library feature will not operate when executed on an unsupported OS or hardware.

If the feature is not essential to the library a warning message in the logs should suffice. If the feature is critical to the operation of the whole library an exception should force the developer to deal with the unsupported case on his own.

7. Be Predictable

App stores make it easy for the app consumers to leave feedback. An app that has a nasty side-effect (downloads too much data, consumes too much battery, randomly freezes or crashes) is very likely to be voted down. This means that the library vendor has to be extra careful not to become the cause of bad reviews.

One of the ways to prevent this is to be very explicit about what would the library does. Complete and up-to-date documentation will ensure that the library’s users know exactly what is going on.

Method names and signatures may also help to understand what is going to happen. For example,

Collection<Item> loadInventory()

suggests that inventory items would be loaded synchronously and returned at once, while

loadInventory(InventoryCallback callback)

suggests that items would be loaded in the background.

8. Create Examples and Demos

A library vendor cannot assume that his users know exactly how to integrate and use his code. Even if the library has a simple API, a set of examples describing basic and advanced use cases is essential.

A demonstration application also helps a lot. The user should be able to launch simple applications that showcase the basic functions of the library. It should work with zero code written by the user. Once the demo app is up and running the rest of the integration is a lot easier since there is a reference implementation to be consulted.

9. Keep It Small

Mobile connectivity rapidly becomes better through adoption of bigger data plans and faster networks. But still, it is a good practice to try to keep your library size down. One should keep in mind that their library is probably not the only one integrated into the target app. And with 5-10 different libraries app size might increase quite a bit.

One of the common reasons for a library to swell is a multitude of other libraries included at compile time. Those libraries are usually included to make integration steps easier: the library becomes a self-contained object ready to be dropped in. It is a compelling argument, but adding extra stuff should still be avoided when possible.

10. Eat Your Own Dog Food

App markets’ regulations change quite often. The number of mobile devices grows and hardware becomes more augmented. New OS versions keep introducing new nuances in OS behaviors. This makes it important for a library developer to have one or two in-house applications that are appealing to the end consumer and test basic functions of the library in the real market conditions. It is a lot better to suffer consequences of a bad version on your own apps before harming your users’ apps.

The in-house app does not have to be a market hit. It has to have just enough users to cover most of the diversity in the market.

Basil Shikin is AppLovin’s VP of Engineering.

We’re hiring! Apply here.