Best Practices for building a world class Mobile SDK
Good Mobile SDK craftsmanship is a topic that often comes up with teams aiming to perfect their SDK strategy. The benefits of a well built SDK are clear. Improved developer experience, faster integration and rollout of new features, all which ultimately leads to a higher adoption of your product.
Having built a number of Mobile SDK’s in my career, I’m often asked for advice on this topic. Whilst there are no hard and fast rules, there are a number of best practices that have been picked up along the way to help build a best in class Mobile SDK whether we are talking a .aar file, .framework or otherwise.
The following will address key points such as simplicity, transparency, security, frugality, documentation and communicating with your users. As an SDK you are simply a guest in your host app so you have to put on your A-game and behave like you want to get invited back. So without further ado, here are our best practices in Mobile SDK development.
Design and interface
- Aim for simplicity.
Provide only the methods that are needed and do not bloat the SDK. Whilst an SDK should strive to provide all the required information, data and resources to help developers integrate your service in a seamless manner, it should take into account what the common use cases are. Therefore, aim to provide methods for these common use cases and abstract/hide away from the developer as much as possible regarding the inner details of the SDK. Initialisation of the SDK using your credentials should not take more than one line.
2. Adhere to platform naming conventions.
Follow the standard platform conventions, so that variable names, methods and design patterns are all consistent with what the app developers are familiar with — so the SDK feels like a natural extension of the platform. Avoid reinventing the wheel by introducing new conventions that may be alien to the developer.
3. Aim for parity across platforms.
Attempt to keep the SDK features and methods as uniform as possible across platforms. For example, if the Android SDK has a method getUser that returns a User object, then the iOS SDK should follow suit. This will aid not only help your SDK users but also make the lives of your test/QA team easier and more efficient as it will reduce the number of unique test cases they have to create for each platform.
🚀 Furthermore, mobile developers often use cross-platform development frameworks, such as React Native or Xamarin. Having consistency will enable them to quickly create a wrapper for the SDK to use in their cross-platform framework of choice.
Granted this can seem to contradict with the point above, so the key is to strike the right balance, such as having methods and callbacks with the same functionality — even if the platform naming convention or language syntax is different e.g. using delegates vs callbacks.
4. Clear status codes.
Low level errors and exceptions should be handled within the SDKs with clear status or error codes returned back to the SDK user via delegates or callbacks. Align the status codes around the standard HTTP return codes conventions. If your SDK is supported by an underlining API (as most are) then have these status codes as an extension to your API HTTP codes.
The status code meaning should be clear and helpful and provide contextual information to resolve any errors faced. This is essential for quickly assisting the SDK user for resolving any issues that might be faced whilst integrating and will hopefully reduce the number of support tickets you receive.
Decoupling the status codes into their own file or class (as opposed to having them scattered across your code base) leaves them easier to manage and update as your codebase progresses.
Resources and performance
5. Your SDK is a guest in it’s host app.
It goes without saying that any software running on a mobile device needs to be aware of the resources it is using. A well built mobile SDK should not negatively affect the performance of the host app that is using your SDK. The ideal scenario here is to provide maximum functionality whilst using the minimum amount of resources but finding this balance is tricky. Releasing a version of your SDK that negatively affects the host apps performance, is likely to have your SDK dropped/pulled from the next version.
6. Be considerate with threads.
You will likely have methods that will perform heavy or long running operations. These should be called only on worker threads and should not block the main UI thread.
7. Check for memory leaks.
Best practices for memory allocation should be adhered to and memory leaks should be avoided at all costs. This needs to be validated prior to shipping, both by using profiling tools (such as Instruments or Android Profiler) along with rigorous performance and stress testing. This testing should be performed on a wide range of real devices and include a good percentage of low-end devices.
🤖For Android, don’t assume that the garbage collector prevents all memory leaks.
8. Don’t drain the battery.
Avoid actions that will drain the end users phone battery, such as persistent network polling, intensive background processing or frequently requesting location updates. Pay attention to the devices power state, if the device is in low power mode, avoid any battery intensive actions.
🤖For Android, use wakelocks sparingly and avoid excessive wakeups. The Android framework allows jobs to be intelligently scheduled across apps, meaning “disparate” tasks can be combined so that overall battery consumption is reduced.
9. Be thrifty with network usage.
Consider how often you need to be requesting data from a network API. If you need to fetch new data on a regular basis consider synchronization strategies. If the API you are using supports a partial or incremental synchronization, you will be able to download the full data set once, then request updates passing in an id or timestamp, as opposed to downloading the full data set each time. For large downloads, consider waiting till the device is charging and connected to WIFI.
Requests should be thrifty, particularly if the device is using cellular data. Not all end users will be on unlimited data plans and will likely be limited to a certain amount of data each month, and there is also no guarantee about what the network conditions will be.
10. Do not rely on your API.
An SDK must not crash or have any unexpected behaviour caused by either lack of connection or undefined API behaviour such as 500 errors.
11. Be aware of size.
An SDKs size is not just the file size of the .aar or .framework file size. To get a realistic estimate, compare the file size of a sample host apps .apk or .ipa both prior and after integrating the SDK. This is due to a number of factors that need to be taken into account, such as including all required architectures and external dependencies, whilst optimising and removing any symbols, unneeded architectures or any unused methods (depending on platform).
🤖For Android, consider providing an example proguard config file.
For any included resources (such as images) use techniques to optimise their file sizes.
Transparency
12. Be transparent with what your doing.
Do not hide things from the user. While hiding methods for simplicity or protecting your IP is good practice, reporting analytics, operating in the background, doing anything outside of the scope of your SDK is not OK. Your users deserve to know what they’re getting into.
13. Be transparent with permissions.
With both iOS and Android 6.0 Marshmallow onwards, mobile apps have to ask the user for permissions one-by-one at runtime, but permissions can be revoked at any point, so make sure your code handles this gracefully. Respect the permission framework, the permission request should come from the host app itself and not the SDK.
Consider adding an explanation to clearly articulate why a particular permission is needed for your SDK to function into your documentation. App developers can incorporate this into a primer to explain to the end-user why the permission is needed prior to asking for it which should result in a higher opt-in rate.
Plan for the future
14. Stay up-to-date with new versions.
Ensure your SDK is compatible with the latest OS version and architecture. It is recommended to pay attention to the release dates of new OS versions and test your SDK on the developer previews ahead of time taking into account any new OS guidelines or conventions.
15. Communicate new features and deprecate old ones.
Over time you will want to roll out new features or introduced a better way of doing things. Deprecate old features rather than outright removal. Almost all languages have special tags to tell when a method was implemented, when it’ll be deprecated, and show warnings if it’s used.
Keep deprecated methods for a few more releases just to give everyone a chance to migrate away from them before before their final removal.
16. Backwards compatibility.
At launch time you will have to set a minimum version of the OS that your SDK supports. Do not later down the line remove support for minimum OS versions on a whim, you must be absolutely certain that the end user basis for that version is small enough to drop support. Bear in mind that if you have a SDK integrator with a very a large user basis, what you consider to be a relatively small segment of end users might actually be significant enough of a number to warrant it’s continued support.
17. Maintenance mode.
However, if support for older Architectures or OS versions is slowing down the development process too much for the sake of a only handful of users, consider putting that version of your SDK into maintenance mode.
For the next version your release, drop the support for the Architectures or OS version causing the headache and update the major semver number. Keep the old version available for download and put it in maintenance mode only, whereby it receives critical bug fixes only. Be sure to communicate this to your users ahead of time.
18. Carefully consider if third-party external libraries are needed.
Using external libraries provide convenience and speed in the short term but can leave you with technical debt later down the road. They can be the source of unknown bugs, leave you inherently slower at adopting new OS features or standards than you would be with your own code and once integrated, they are never easy to remove. Furthermore, you need to consider any licensing restrictions and if they introduce any privacy or security risks.
There is often the temptation to include a flavour of the month library, but consider if it really is needed. If you really must use it, then consider changing the dependencies class names to avoid collisions if the same third-party library is used by the host app and be prepared for updates to the third-party library causing issues later down the line.
Security and Protecting Intellectual Property
19. Take security seriously.
As SDKs are typically deployed horizontally in an app store ecosystem, a single SDK deployed in multiple apps could compromise the security of all those apps, meaning an SDK has a much wider SDK attach surface than that of a single app.
20. Use SSL.
Enable SSL for all networking calls to encrypt HTTP traffic.
21. Use Certificate Pinning.
SSL on it’s own is susceptible to Man-In-The-Middle attacks if the X509 certificate is spoofed. Therefore implement Certificate Pinning whereby the expected server certificate is already known to the mobile SDK meaning if can compare the known certificate with that of the remote server when it needs to access it, if identical then the connection is valid and the data transfer can proceed.
22. Encrypt sensitive information stored locally.
Try to avoid storing sensitive information locally, however if you have to then encrypt it securely and purge it when done. Keep in mind that Cryptography can be computationally expensive and remember the first rule of Cryptography “Don’t invent your own Cryptography”.
23. Hide symbols.
There maybe cases when you want to hide the internals of a binary library if it contains significant Intellectual Property. You can use the strip command to strip out the symbols of all methods other than the ones you explicitly want exposing by declaring them static. Same applies for Android if using the NDK.
24. Remove logging from release builds.
Log statements can be useful for debugging when in a developer environment but should be removed from production as they will expose the inner workings of your SDK, compromising both IP and security.
Documentation materials to support integration
25. SDK class documentation.
Good SDK documentation is essential. All of the methods you want the SDK integrators to use need to have clear and robust documentation. Using code description formats, such as Appledocs, JavaDocs, KDocs etc, make it far easier to write and maintain good documentation during the development process.
26. Getting started guides and sample code.
These are perhaps the most important for getting developers up and running with you SDK. A lot of the time developers will copy and paste this to get something working quickly. Aim to provide examples for all key use cases of the SDK.
27. Semantic versioning.
This tends to be the versioning system that the majority of developers are familiar with. it provides insight into the purpose of the update and the amount of effort to integrate it.
For example an increase in the patch number typically indicates the release is for bug fixes and will slot straight into the developers code, whereas an increase to the major version is likely going to include breaking changes where the developer will have to update their code. See here for full details.
Do not skip version numbers for any superstitions, for example 4 is considered unlucky in some parts of the world. The reasons for why will not be clear for users in another country or timezone.
If you are introducing a new version with breaking changes, do your users a favour by providing a checklist outlining all the steps they need to take to migrate to the new version.
28. Package for distribution.
How you package the SDK for distribution will play a crucial role in it’s success as this is the first developers will see of it. Naturally, you will want to package up the SDK libraries themselves along with all documentation and any sample apps. Additionally a readme and licence maybe supplied.
Consider making CocoaPods and Maven packages and uploading to a central repo which will help the developer keep the SDK up-to-date. Provide clear instructions on how to include your SDK as a depency either by Gradle or your Podfile.
Build process and QA
29. Automate the build process.
As is standard practice with most development teams, automate the SDK build process and have this running on a Continuous Integration server, such as Jenkins or Hudson. This will remove any human errors and flag up straight away during the development process if any interfaces or unit tests have been broken.
Additionally, this can also build the full SDK package that you will ship, e.g. the SDK libraries along with all documentation and any sample apps, along with any app that you may use for manual QA at the same time.
30. Take advantage of Automated Device testing.
A mobile SDK needs to be perform well across a wide range of devices, typically spanning numerous models and manufactures and also different versions of the OS. Automated device testing comes in handy here, frameworks such as Appium & Calabash allow you to write automated functional and acceptance tests. Typically this is in the form of end-user actions such as touches and gestures to complete a specific task such as signing in a user.
31. Device Farm testing.
Automated Testing mentioned above whilst this can be done in-house if you have the budget to spend on a good number of devices though typically you may opt to outsource this to a Device Farm such as the AWS Device Farm or Xamarin Test Cloud. At the time of writing the AWS Device Farm has 344 unique device configurations. The output is a full report outlining each tests success on a given device along with screenshots and other useful metrics, such as logs and CPU usage.
32. Custom build parameters.
You may find that you need to support custom builds for particular clients, this could include anything from the likes of branding to etc. Automate as much of this as possible.
33. Manual QA.
Automated QA is great though it’s unlikely you will get to a point where you can fully automate everything meaning manual QA from a human will be inevitable. There is no substitute for the human element, which gives a more accurate testing of real-world scenarios and finding edge cases that the automated test scripts may have missed.
Dogfooding and ongoing support
34. Dogfooding.
Dogfooding is the practice of using your own product and is essential for keeping the developers building the SDK in touch with the developers that will be integrating it into their apps. So for an SDK team this means either building applications with it yourself or being close to people who are, such as a friendly community of beta testers. Consider providing early access to the latest stable builds to select users for this purpose which may also have additional marketing benefits.
Using your own software both alone and with other users, is a great way to determine if you’re building it the right way, but also if you’re building the right thing.
35. Listen to developers.
Provide a mechanism for developers to get in touch and pay attention to that feedback. Multiple support tickets all regarding the same issue probably means your documentation could be better. Updating the documentation is often easier than continuing to responding to the same queries coming in again and again. Also consider monitoring tags in Stack Overflow and other places developers hang out.
36. Bug fixing and patch releases.
Outline your policy for fixing bugs in an SLA and commit to the timelines. Provide support and workarounds for any critical bugs discovered in production until a fix can be released.
If your SDK is causing it’s host app to crash, it’s very likely it will get pulled from the next release of that app unless a fix is provided.
Consider adopting a Zero Bug policy. This means that when a bug is raised you either commit to fixing it right now or you close it as a “Won’t Fix”.
Finally
A well built Mobile SDK makes developer’s lives easier and wins them over to using your product. Once integrated into the host app you’re likely in there for life!
Please comment and contribute to this guide to improve this article.
Thank you for reading and hope you enjoyed our best practices in Mobile SDK development. If you found this article useful, do get in touch. I offer consultancy and pro-bono advice to startups and scaleups. You can find out more about me and contact me here mikesmales.com
If this article helped you, then please buy me a coffee (via KoFi) ☕️😀
Special thanks go to Quentin Roquefort for his contributions. You can find out more about him here.
Finally, special thanks to the team at chirp.io.