Experience with building modular SDK

In conichi we are trying to improve the guest experience in hotel: speed up the check-in & check-out process, brings the mobile payment possibility and so on. To build this infrastructure we’ve been working on three main projects and one of the most important is our mobile SDK, which we are integrating into our partner’s apps.

We’ve been building SDK with one key point in mind it should be modular. And by word modular I mean every partner should have the possibility to construct their SDK with the only functionality they need.

How we made it possible in code

First of all, we detached our core functionality and made initialization process based on dependency injection with configuration object:

@interface CNISDKConfiguration : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *apiKey;
@property (nonatomic, copy, readonly) NSString *apiSecret;
@property (nonatomic, assign, readonly) CNISDKLogLevel logLevel;
+ (instancetype)configurationWithBlock:(void (^)(id<CNISDKMutableConfiguration> configuration))configurationBlock;
@end

This object allowed mutable block-based initialization and is conformed to NSCoding protocol to prevent any reference’s related issues. I took that idea from Parse framework implementation.

Next step was to create unified interface for our “Kits”:

@protocol CNISDKKit <NSObject>
+ (void)enableWithConfiguration:(CNISDKConfiguration *)configuration;
+ (instancetype)sharedInstance;
- (void)start;
@end

This protocol requires methods to enables Kit and configures it, to start underlying Kit’s tasks and to be able to access the shared instance. Moving further each Kit has it’s own initialization parameters which developer could also pass as a dependency injection during initialization

@protocol CNISDKKitConfiguration <NSObject, NSCopying>
@end

Unfortunately, I haven’t come up with yet with any unified properties or methods for Kit’s configuration objects, but this simple protocol at least gives us type-safety and high-level information about how this object looks.

Since the only core module should initialize Kits, we created an object that represents a one-to-one relationship between Kit and its configuration

@interface CNISDKKitBundle : NSObject <NSCopying>
@property (nonatomic, strong, readonly) Class<CNISDKKit> kit;
@property (nonatomic, strong, nullable, readonly) id<CNISDKKitConfiguration> configuration;
+ (instancetype)bundleWithKit:(Class<CNISDKKit>)kit configuration:(nullable id<CNISDKKitConfiguration>)configuration;
@end

And added a new property to core configuration — array of kits

@interface CNISDKConfiguration : NSObject <NSCopying>
. . .
@property (nonatomic, strong, readonly, nullable) NSArray<CNISDKKitBundle *> *kits;
. . .
@end

In the end, we think this approach pretty flexible and easy for developers to integrate our SDK. Lets step aside from coding and next chapter will tell you how we also were able to split our source code.

The power of private CocoaPods

Since our source code unfortunately not open sourced to everyone we’ve been using private Pods to share the SDK, and eventually, we did almost the same as with code. We split our Podspec into four different.

Podspec’s dependency

With that approach, our partners have the power of CocoaPods dependency management and opportunity to not pull unnecessary source code.


In the end, I would like to say that this is not a perfect solution, and maybe you, my reader, know something better — any feedback is greatly welcome!