Twitter’s Fabric is well-known for its focus on SDK quality, and has been deployed on billions of devices. In this Øredev talk, Ty Smith, also from Twitter, uncovers the principles that the Fabric team uses to make their SDK great, with focus on Android. Through a deep dive into technical decisions the team makes, Ty show what his team has learned about developing an SDK for stability, testability, performance, overall footprint size, and, most importantly, exceptional ease of implementation. Whether you are building an SDK now or in the future, you will find some great ideas in this talk for API design and usability.
APIs & Fabric (0:00)
As developers, most of us have had to use SDKs and APIs that were frustrating or not well designed. SDKs are at the heart of great applications and great developer experience goes a long way when making great apps. At Fabric we care deeply about the developer experience, we spend enormous amounts of time trying to make our products easy and fun to use. My name’s Ty and I’m an Android engineer on the Fabric team at Twitter. I’d like to share with you some highlights that we’ve learned while building out SDKs.
What exactly is Fabric? It’s mostly a set of modular SDKs built by Twitter that share a common mobile and web platform for a reduced footprint and a consistent approach to solving problems. We released Fabric to the world last year and it’s completely free. We believe it’s a great way to bootstrap your mobile apps. Just last month we announced multiple external partners who are now building on top of the Fabric platform as well, with many more coming soon.
As we were building out the Fabric SDKs, we kept several goals in mind that helped guide us as developers. These principles compelled us when making API and programming decisions. These ideas can be incorporated into your own SDK or even your application that you’re building. I hope you’re able to take some great ideas away from this, whether you work on SDKs currently or you will in the future.
Considerations (1:19)
Before we hop into code though, we have to think about a few considerations that I think you should make before you try to build a library or an SDK.
Customer needs (1:27)
The first consideration is to figure out what need you’re actually serving with the library. Is it for internal developers or public developers? Who’s gonna be using it? What is the value you’re adding? Is there already a solution on the market? If so, you should be contributing to that instead.
Open vs. closed source (1:42)
Considering open source versus closed source is also a big question. Open sourcing will generally give you better adoption from the community, more stable software, and more enthusiastic internal engineers. However, it’s generally questioned if your SDK is solely engineering focused or if it’s an entire product, and complications arise when you have a backend service.
Therefore, consider your license carefully as well. For example, if you had a GPL license that may force any consumer of your SDK or library to also GPL license that way. A more flexible license might be something like Apache 2 or MIT.
Binary packaging (2:18)
On Android specifically, packaging your code isn’t necessarily straightforward. You have three types to consider. The first is a standard library project, which is where the developer just includes your source code and links it in their IDE. It’s very flexible, however it has the least number of guarantees: what if they fork it, how do they stay up to date on updates?
Jars are another great example in the Java world – very standard binary packaging. Unfortunately with Android they don’t bundle their resources, so it might rule it out for many view-related libraries on Android.
Lastly, you have AARs on Android, the sanctioned binary from Google right now. It’s a zipped container with the compiled source code as well as the resources in that, and it can be hosted through Maven so that it can be used with Gradle Maven or Ivy or any sort of dependency resolution system.
Hosting the artifacts (3:10)
The last consideration is where to host your artifacts. Maven Central, and JSON are standard repos that get referenced in the build tools out of the box. However they both require open source licensing because they wanna protect the users of their service; they don’t want someone to implicitly pull down a binary and get opted into a terms of service that they didn’t get to review. If a customer repository is used (if you have a proprietary binary), then the developer must manually add the repository in their build script.
Building Great SDKs (3:38)
Working on SDKs in Fabric has been a fantastic learning process, and these are the five aspects of great SDKs that I will cover: easy to use, stable, lightweight, flexible, well supported. We believe that great SDKs go a long way in delivering on all of these points.
Easy to Use (3:50)
One of the big areas of focus is usability. We believe that developer products should be easy and easy to use.
What do we mean by easy to use? We wanted to create the easiest way possible for people to get started with Fabric right away in their application. When it comes to easy, it doesn’t get a whole lot better than this. With just one line in your application file, you can get going.
Fabric.with(this, new Crashlytics());
But as easy as that is, many developers want more customization. To do that, we use the builder pattern to set commonly used but optional parameters, like a listener to notify you when the app previously crashed.
Crashlytics crashlytics = new Crashlytics.Builder()
.delay(1)
.listener(createCrashlyticsListener())
.pinningInfo(createPinningInfoProvider())
.build();
Fabric.with(this, crashlytics);
With Fabric we require an API key as a dependency to authenticate against our web service. This is something that we wanted to handle for the developer to minimize the amount of work needed to get started. Our standard approach is to provision that key through our build plug-in and inject that into the manifest. And here’s an example of the manifest with that metadata inserted.
<manifest package="con.example.SDK">
<application>
<meta-data android:value="01235813213455"
android:name="com.fabric.ApiKey" />
</application>
</manifest>
Once Fabric is in the initialization process we can parse the application info provided by the package manager to retrieve the API key and continue along in that process. We do, however, allow alternative approaches as well to managing the API key, and this can work much better for open source libraries. For example, you can create a properties file and then we’ll read that directory at runtime.
Usability traits (5:05)
Aside from the implementation details that I just mentioned, we like to think about these sort of traits when designing APIs. The first is being intuitive. If an API call acts exactly the way a developer expects without having to reference documentation, it will avoid surprises.
We find that consistent naming in the APIs of your SDK also help facilitate understanding. Use common language in the method signatures and similar design patterns within your SDK. Also use naming conventions according to the platform, for example, iOS or Android.
Lastly, APIs that are hard to misuse prevent bugs from happening. Validating the inputs and making required contracts explicit will make the developer using your SDK feel safe and confident that they have an understanding of what’s going on and what to expect. They’ll have a much more pleasant experience.
Intuitiveness (5:56)
Let’s look at some code examples of this. First let’s cover intuitiveness with a fun example, a counter example.
URL url1 = new URL("http://foo.example.com");
URL url2 = new URL("http://example.com");
url1.equals(url2)
This is one of the hardest but deepest effects of what it feels like to use an API. When using a great API, we can guess exactly how it’s gonna behave. Here we would expect equals to perform some sort of normalized string comparison.
Turns out that if those two URLs resolve to the same IP address, in certain implementations of Java, that will return true, cause here’s what’s really interesting about this implementation of the API: it fires a synchronous DNS request. Would anyone expect that? Blocking the caller thread is an example of unexpected behavior that should be made very explicit in the API.
Consistency (6:38)
An example of some APIs that are consistent in the Fabric platform is the initialization of Fabric and Crashlytics. In initializing either Fabric or Crashlytics, two different binaries, we allow the same pattern builder pattern as we saw before. The user can get the defaults by using a no parameter constructor or a helper method defined, however, both classes offer a builder that can be used to heavily customize the objects.
Hard to misuse (6:59)
Lastly is hard to misuse. For example, the constructor from the builder of Fabric like we covered earlier requires the context to be set, while other setters are options. Once we’re in the build stage to create the instance, these optional parameters are initialized with default classes.
The developer consuming the API cannot proceed without providing the context, but can use the others at their leisure. We believe this is hard to misuse.
How We Design APIs (7:30)
How do we get these qualities though? Let’s look at our design process to find out. Designing APIs is hard; it’s usually not just one engineer that sits alone in a dark room and decides what’ll look like, but it takes a lot of work from the entire team.
The first focus we strive for in building the API design on the Fabric team, is alignment in what we’re building. We create a design doc before any implementation work is done so that we can discuss the pros and cons of the different approaches across both of the platforms that we work on.
There’s a saying that I like: an API is like a baby. They’re fun to make, but they take 18 years to support. Any API we add is going to have to be supported for a long time so we want the confidence that we’re building it the right way.
Lastly, although we prioritize a similar brand across our iOS and our Android features, we build platform first to make out developer feel most comfortable where they’re working everyday.
API Stability (8:26)
Now that we’ve designed something that’s easy to use, let’s talk about how we can gain developer trust, which we believe is extremely important. For that, stability matters by ensuring that SDKs are reliable and they don’t affect the stability of the application itself. You all know that stability is always important, but it’s significantly more critical when building an SDK than when building an application. Let’s look at why by looking at the lifetime of a bug.
If an application has a critical bug that’s impeding its users from using it, this is the time that it may take to put an update out there all the way into the hands of customers. Even if we discover the bug early and we fix it fairly quickly, it may still take up to a month, during which time your users have a degraded experience.
However, if an SDK has a critical bug, the timeline is a lot longer. It may take months before users of an application using your SDK get that bug fixed. Since app developers may take weeks before noticing or upgrading your SDK to the version that has the bug fix, ensuring high stability in an SDK is one of our highest priorities.
How can you ensure stability? (9:36)
What can we do as developers to ensure the highest stability possible? There are some things that are key parts of our development process. Code reviews are always important, but we take them way seriously. By constantly asking ourselves “what can go wrong with this code?” we try to be as defensive as possible.
Being able to automatically have some sort of guarantee of basic correctness, can also help catch bugs early so unit tests are incredibly useful.
Another side that’s often forgotten is making it possible to run some of your SDK code inside your user’s testing environment. This way they may be able to catch bugs in the integration with your SDK.
Lastly, continuous integration and dog-fooding also add another layer that may help identify problems early and quickly.
Making SDKs testable and mockable (10:16)
There are some tricks to making your SDKs testable and by testable, I mostly mean mockable in this case. A mock class is just a dummy class that represents the real one, but has no ops and allows for overriding returns for the method calls and other types of verification on the calls.
By avoiding static methods, you allow any method calls to also operate on the mock instances. If you’re going to utilize static methods, though, make sure that it can be tested in isolation and that you’re providing all of your dependencies to it upfront through the parameters, and there’s nothing state-based around it.
Many mocking libraries also have problems with final classes so be thoughtful about your class extensions. Public fields won’t exist on your mock classes, so everything that needs to be mocked should run through a method for access.
Use interfaces around your public API. It’s much easier to set up classes to test if your entry point uses interfaces. The interfaces allow developers to override with behavior to hit mock servers or in memory storage instead of expensive and inconsistent real operations.
Lastly, think about architecting your code in such a way that a tester would never need to mock more than one level deep. This encourages tests to actually be written and provides more stability to your testing framework.
Testing by example (11:39)
A class that is hard to test in mock is one that is final, creates its dependencies on its own, and is a singleton, meaning state-based. This is pretty common in Java, although it’s generally an anti-pattern. It makes it very challenging to test in isolation. So, what could we do to this to fix it? With just a few modifications we can make this a lot more testable.
public class Tweeter {
private Network network;
private public Tweeter(Network network) {
this.network = network;
}
public List<Tweet> getTweets() {
return getNetwork.getTweets();
}
}
Instead of making this a singleton, why don’t we make it an instance? The developer can reuse themselves, cache, or do what’s responsible. Remove the final modifier in the class so that Mockito or other frameworks can mock it and allow the SDK to take its dependencies at construction time. Dependency injection isn’t just a framework, it’s a design pattern to help organize code around better modularity and testing.
Graceful degradation (12:29)
Developers are impatient and inquisitive, so the faster something fails, the better. If you’ve been using Gradle you’ll know what I mean after you have a build that fails after five minutes of build resolving. You should throw expected exceptions so the developer can know there’s a problem very quickly, and in this case the developer tries to set a null logger on our builder, we’re gonna throw an exception so they recognize it quickly and can resolve their mistake.
However, in production your SDK should never fail, preserving the trust of your developers is the only way to keep your code in their application. Their app is often their livelihood and they won’t bat an eye at replacing a crashing library. So, you can provide the developers the extra info when they’re debugging by failing fast, with a clear exception, but concealing potential problems in production will allow the rest of their application to continue running. Your SDK being disabled may be a big deal to you, but it’s not the end of the world.
As a developer, you use SDKs that add value to your app, the worst thing that someone could do is take away value or destroy the user experience altogether. Developers, myself included, don’t need anyone’s help writing crashy apps.
Lightweight (13:43)
In addition to being stable, users are less likely to download big apps and this means that binary size matters. Unlimited data plans have gone in and out of fashion around the world, many users pay per kilobyte, so downloads cost them money even if your app is free. In many emerging markets, the cell connectivity is even too slow to download a large app and in some markets, users actively choose what updates to apply based only on the change log and what new features were added because they’re paying per kilobyte.
Let’s talk about some techniques that Fabric uses to keep our SDKs lightweight. There are some great third-party libraries out there that can really contribute a lot to your app, but they aren’t free when it comes to the size and impact. For example, popular image loading libraries on Android vary greatly in size. Fresco, for example, is an order of magnitude larger than any of the others. However, it has much better support for older devices, it bundles an entire native image process pipline for processing, and it supports progressive JPEGs.
As an SDK you should strive to balance your size with your functionality. So, be mindful of including third-party libraries to make sure that they only meet the goals that are needed.
There are great advantages to using open source libraries like these, they’re tested, they’re well used, and they have communities contributing to them regularly. This generally provides a much better experience than rolling your own. In our Twitter SDK, for example, we use RetroFit by Square as a dependency to simplify our APIs, and this allows us to provide extensibility, which we’ll get to in a bit.
Reporting binary size (15:22)
task reportSdkFootprint << {
def sdkProject = project(':clients:SdkProject')
def nonSdkProject = project(':clients:NonSdkProject')
def footprint = getSizeDifferent(
new File("$sdkProject.buildDir}.../Sdk.apk"),
new File("${nonSdkProject.buildDir}.../onSdk.apk"))
println footprint
}
There’s an easy weight loss hack that focuses on data collection. If you just weigh yourself everyday, you will lose weight. We take this approach to our SDK sizes. We actively monitor them on each build so that we can weigh the true impact of a developer’s APK and graph this over time. There are no hard and fast rules around size increases, but it’s generally helpful to know how a specific commit increases the size and investigate if that was without warrant.
Dalvik method limit (15:52)
Has anyone in the room actually run into the Dalvik 65k limit? Okay, few of you. For those of you who aren’t familiar with this error, it’s a finite limit on the number of method invocations that are allowed to be referenced by a single dex file in Android. The problem surfaces when the dex tool, at compile time, tries to write all the method references to a certain amount of space allocated for this in the dex file and it fails.
You can now use multidex, but out of the box it increases app loading time on older devices, it increases build time significantly, and it isn’t suitable for folks outside of some of the newer Gradle users. It is even incorrectly implemented on certain popular Samsung devices and causes app crashes on launch every single time, consistently. These specific devices have millions and millions of devices that are active in Europe and Asia, so that says something.
But if a developer runs into this problem, though, before they go with multidex or something similar, often times they decide to audit their choice of third-party library to see if they can minimize their app size. Our goal, and my advice to other library or SDK developers is to limit your impact here as much as possible and be extremely modular and lean.
We use a great library called dex-method-counts on our build script written by an engineer at Quip, and it wraps some of the Android build tools to give a detailed data analysis on method count per package. This allows us quick insight into our size and the size of our dependencies. We have this set up on CI to run every build and report the data just like we do with the binary sizes.
Modularity (17:31)
Even though we want modularity for developers who need specific functionality, we wanted simplicity for developers that wanted to include entire set of features into their app and be up and running very quickly. We do this by specifying a tree of transitive dependencies. So, we have two examples for initializing Fabric:
Easy to integrate:
Fabric.with(this, new Twitter());
More control:
Fabric.with(this, new TweetUi(), new TweetComposer());
The first example gets you started right away, the second is a much more customized version, where you can pick the specific components that you need, and I’ll dive into these.
To keep our SDKs as small as possible, we focused on this modular approach when it came to our architecture. Like we discussed, on Android both binary size and the method count are very important, so this allowed us to be as efficient as possible. Because if we take advantage of AAARs, a standard format for delivering libraries via Maven’s dependency resolution, we can use transitive dependencies to meet our needs here.
In the architecture for the Twitter SDK stack on Android, everything starts by being built on top of Fabric, providing common shared code for all of our SDKs. Then we build out the TwitterCore layer, which provides log on and an API client and some other core functionality to the Twitter specific experience.
Then we have our feature based SDKs, like our embedded Tweets, our composer, our SMS sign-in infrastructure; these are all of the features that you would, as an app developer, use the SDK for. Then lastly, we provide an interface artifact that wraps it all together through transitive dependencies. This allows the app developers to pick and choose only the components that they need in the hierarchy.
Compressing data (19:04)
Outside of the SDKs sizes itself, we next recommend that you compress data before it hits the wire. We can see that there’s significant size difference between uncompressed formats, like XML and JSON, and the compressed formats of those using JZIP, or a binary format like Protobuf. For those of you not familiar with Protobuf, it’s a compressed binary format where the server and the client agree on an interface ahead of time so that the communication doesn’t need to transfer all the field information. They can get very efficient and fast to serialize and deserialize.
Batch requests (19:36)
Cell radio is one of the biggest sources of battery drain, so making sure that we’re transmitting efficiently is key to avoid draining too much power. A typical cell radio on Android has three energy states: full power, low power, and idle or standby.
Every time you create a new network connection the radio transitions to a full power state and it will remain in the full power state for the duration of the transfer, plus an additional five seconds where it’s waiting for more transfers, followed by a 12 second period of time where it cycles down into the lower power state where it’s more effective to transfer back up to the high power state, if another connection was to come through, before finally cycling back down to the idle time where it’s draining the least amount of battery.
For every data connection on a typical 3G device, the radio is alive for about 20 seconds. That means if you had three connections per minute, we’re keeping this cell radio perpetually active. By batching those three connections together we can reduce the amount of time to 20 seconds of enabled rate here and almost 40 seconds of standby or idle time.
An example of this would be a naive analytics SDK that pings the server every 20 seconds just to acknowledge that the app is in the foreground for the user, and it will keep the radio powered on indefinitely, resulting in significant battery drain and almost no actual data transfer.
Asynchronous tasks (20:54)
Part of keeping Fabric lightweight for us was knowing when to do work on the main thread and when to get it onto the background thread, because we suggest initializing Fabric at the start of your application and it’s on the main thread, we’ve heard many concerns from our customers around start-up time.
To alleviate this we do a very limited amount of work synchronously immediately and then we return the execution flow to the developer’s application, while we continue to do the longer running work asynchronously to keep your app starting quickly.
An example of some things that need to be done synchronously: if you’re using Crashlytics then you want to install the crash handler almost immediately, otherwise you won’t be able to catch crash exceptions if that happened before the async operation was finished.
Flexibility (22:36)
An SDK developer doesn’t have the same luxuries as an app developer. You don’t get to pick your devices, API levels, or customers. You need to provide maximum amount of flexibility to support a broader range of devices so app developers aren’t limited when choosing your SDK.
Build tools support (22:53)
An example of flexibility the developers appreciate is providing support for different dependency managers and build tools the developers may choose to use.
We provide plugins for major Android build tools including Gradle, Maven, and Ant. We also provide GUI plugins for the common IDEs and a Mac app for iOS and OS X development. And although this is mostly an Android talk, recently we were pretty excited to announce that we have CocoaPods support to install our SDKs for iOS users.
Choosing your minimum OS version (23:26)
Flexibility is all about understanding the needs of the users of your library. One of the decisions to make is which minimum OS version to provide support for. Ideally we want our SDK to be adopted by the maximum number of applications as possible. For that, lowering the minimum OS version can actually help.
On the other hand, this comes at a cost. There’s no magic rule for this one, one must balance these two ends of the spectrum of adoption and complexity. Supporting old OS versions often mean not taking advantage of bug fixes and APIs that facilitate implementation. It also adds non-trivial complexity to testing and verifying the correctness of your SDK.
In general, an SDK may try to be more conservative than an application to increase adoption. For example, Crashlytics still provides support for Android 2.2, since it’s such a basic utility for all Android apps. Rather than looking at the number of applications which still support a certain OS version, pay attention to the number of users using those applications for your specific app developers. Sometimes you may find that a small number of applications can drive a lot of users of your SDK.
Another important part of detection is being able to figure out what version of Android you’re running on, so you can know which methods are okay to call. Often, SDKs support much older versions of Android SDKs, since you don’t have insight into your end users. This example of Build.Hardware field was included in Froyo, so calling it before it would result in a runtime exception. This is incredibly important for us, since we wanna provide the maximum amount of device support.
Android Manifests (24:57)
On Android, runtime compatibility is an important part of flexibility. As any Android developer in the room will know, the metadata for an app is declared in the Manifest file, and there’s one of these per APK, the binary format for Android apps.
Additionally, a Manifest is declared and used for each AAR that is included in your project, but at build time, the Android tooling merges the application Manifest and all of the Manifests declared in the AARs. This means that if an AAR declared something that would affect user updating in their Manifest, like they added a permission, it could be in the final build and not be very visible to the app developer.
Permissions (25:37)
Permissions are critical to any Android app. In Crashlytics we utilize the access wifi state permission to better manage uploading of crashes. As of Android Marshmallow, this is classified as a normal level permission, which means it still needs to be declared in the Manifest and agreed to. We do not, however, declare this permission in our Manifest, so it’s up to the developer using us, if they want to declare that because they want the functionality. If the developer declares that permission, though, we’ll see that using this code.
protected boolean canCheckNetworkState(Context context) {
String permission = Manifest.permission.ACCESS_NETWORK_STATE;
int result = context.checkCallingOrSelfPermission(permission);
return (result == PackageManager.PERMISSION_GRANTED);
}
We just need to check the permission on the context object to tell if we have granted that permission. If the permission isn’t already granted by the application, we couldn’t use that protected API without getting a Java security exception. An SDK can check at runtime to determine if the permission’s granted in the application, and use certain APIs if they’re available.
You always need a fallback mechanism, though, and in this case, if we don’t know the state of the wifi or the state of the internet, we have to assume that it’s always connected and try, and let a timeout happen.
Making features optional (26:42)
There are a lot of Android devices out there and a lot of them have features that others don’t. The original Kindle Fire, for example, was super popular and didn’t have a camera. These hardware features like the camera can be a really powerful aspect of Android Manifest too.
For example, if you’re building a camera-based SDK, you’d be tempted to declare that feature in the Manifest. This requires the Play Store not to list that app to any device that doesn’t have a camera. I encourage you instead, to list the feature as optional and allow your library to detect and modify its behavior at runtime.
Detecting the hardware features at runtime is very simple. You just need to query the package manager with the specific feature to find if it exists. Now your app can determine which functionality to enable. For our camera library example, you may still allow the user to browse photos and upload photos, they just wouldn’t be able to take them on this specific device.
Classpath detection (27:41)
There are many cool third-party libraries out there that can really speed up your app development a lot and improve your code. When providing your SDK to developers, it’s tempting to want to use many of these libraries. Maybe you want to provide RxJava support or provide Square’s OkHttp library for a better networking layer. But you don’t necessarily want to include a third-party dependency on that for the lightweight cost that we talked about earlier.
private boolean hasOkHttpOnClasspath() {
try {
Class.forName("com.squareup.okhttp.OkHttpClient");
return true;
} catch (ClassNotFoundException e) { }
return false;
}
In this example, you can check if the class for OkHttp exists on the class path. Instead of using the compile scope in Gradle, use the provided scope, or if you’re using Maven, set the optional flag. This will allow you as the SDK developer to compile against the classes, but not necessarily tell the app developer’s APK to bundle it themselves. If they bundle it, you use it, if they don’t, then you have a safe fallback.
Extensible classes and interfaces (29:16)
In addition to runtime detection, we can’t meet every developer’s needs. Our Twitter SDK provides easy to use and popular endpoints to find, already in the API client. We go ahead and we sign these for you to simplify its use, we use the persisted token that we get from the log in and then we sign all outgoing requests.
But what happens if you wanna hit a more obscure Twitter endpoint that we’re not currently providing? You don’t wanna reimplement all of our client, so instead you could just extend the Twitter API client and provide your own retrofit-style interface with annotations and then we’ll handle the signing for you.
As we all know, developers have quite a range of tools, there’s a lot of options out there, and they need a lot of flexibility in their code. This is especially true as an SDK developer. We provide extensible interfaces for using Fabric so that the developer can utilize these for their own choices.
One example here is logging. There are many different libraries to use and we provide this logging interface so that it can be implemented before Fabric started and then we’ll respect the logging needs of the developer.
But if the developer chooses not to implement that logging interface, we provide a sane default that takes advantage of the standard Android logger.
More than just Java code though, a good library has views that can allow style properties to be overwritten as well. For example, the developer can customize various colors used in rendering a Tweet view on the TwitterUI SDK.
One important note though, is that the dex merging doesn’t name space for libraries on Android. If you don’t name space the attributes yourself, it’s quite easy to conflict with the user specified style unintentionally. So, don’t use generic names like “background color.”
Callbacks (31:09)
Part of making flexible code is allowing the developer to choose to be notified when a certain behavior happens. In our builder, for example, we can set the initialization call back that notifies the user once all synchronous and asynchronous setup is done, or if there was any issues. This provides more information than would otherwise be available. It allows the developer to make custom decisions based on the state and the information that they acquired from it.
Well-Supported SDK (31:37)
The job of building an SDK isn’t done when you ship an artifact. Continuous communication with the developer community using your SDK is key to making your product successful. Both Apple Docs, JavaDocs, and ReadMe type examples are really important for developers to learn your library.
Document every public method and all the expected use cases.
Publish concise sample code, it will be referenced for usage and people will copy and paste it, so treat it as production code. Don’t create a full gallery app just to demo an image loading library, this will force developers to spend more time investigating and learning your sample, and making their own code more prone to bugs using your SDK or library.
Think about how you wanna deprecate older versions and methods. How many of you haven’t looked back at old code you wrote a year ago and cringe and think “who wrote that”? That happens to all of us. Instead of just rewriting it, another refactor, be nice to your developer community by deprecating those specific methods, and then communicating a road map for removal or what you plan to do with that.
Respect versioning, we use semantic versioning on Fabric and I would highly recommend it. It’s a contract for your developers using your library or SDK, to understand when you change the API. And this helps them understand what effort will be required for them, for their team when they decide to update from one of your versions to another.
Lastly, use the communication channels that developers expect. If this is an open source library, be on GitHub Issues, watch Stack Overflow, be on IRC channel for Android on Freenode, whatever it is.
Although we think all these points are really important, they’re really just the tip of the iceberg in building great libraries. We hope you enjoyed getting an inside look at what we think about SDK development in Fabric, and that you find some of these suggestions useful, whether you’re building SDKs now or in the future. Thanks.
Receive news and updates from Realm straight to your inbox