Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Awesome Android SDK Design

Awesome Android SDK Design

https://medium.com/capital-one-tech/awesome-android-sdk-design-fef427604546

So, you’re building an Android SDK (library). And of course, you’re going to integrate the library inside a sample application before publishing in order to test it thoroughly (and use as a demo for Product and Sales team members). As you develop the library, you are bound to have Debug build variant features (SDK tooling) that you definitely do not want to ship in Production code. But, these debug features are extremely helpful for demo purposes and User Acceptance Testing (UAT).

Let’s start off with a thesis:
Testing / Debug code should never be included in shipped Production code.

So, how do you design an SDK with all of your awesome debug tooling and also avoid the riskiness of shipping them “turned off” in Production code?

jacksoncheek

August 15, 2019
Tweet

More Decks by jacksoncheek

Other Decks in Programming

Transcript

  1. @jacksoncheek 1. Application debug features 2. What about debug features

    for SDKs? 3. A wild scenario appears! Outline
  2. @jacksoncheek 1. Application debug features 2. What about debug features

    for SDKs? 3. A wild scenario appears! 4. Leveraging modular SDK builders Outline
  3. @jacksoncheek 1. Application debug features 2. What about debug features

    for SDKs? 3. A wild scenario appears! 4. Leveraging modular SDK builders 5. Demo application that integrates our SDK Outline
  4. @jacksoncheek • Easy testing and debugging • Speed up dev

    time • Helpful for User Acceptance Testing (UAT) Application Debug Features
  5. @jacksoncheek • Easy testing and debugging • Speed up dev

    time • Helpful for User Acceptance Testing (UAT) • Demoing for Product Owners and Sales Teams Application Debug Features
  6. @jacksoncheek • Easy testing and debugging • Speed up dev

    time • Helpful for User Acceptance Testing (UAT) • Demoing for Product Owners and Sales Teams • Do NOT ship to Production Application Debug Features
  7. @jacksoncheek • Custom logger injections Examples Log.d(TAG, "Boring log message

    with a static call to the Android logger :(“) vs. // Android logger in Debug, no-op logger in Release, System logger in Test logger.log("Awesome log message :)")
  8. @jacksoncheek • Mock web server
 
 https://github.com/square/okhttp/tree/master/mockwebserver Examples private val

    mockWebServer: MockWebServer = MockWebServer().apply { setDispatcher(mockDispatcher) mockScope.launch { start(8080) } }
  9. @jacksoncheek • Stub happy paths Examples class MockDispatcher : Dispatcher()

    { override fun dispatch(request: RecordedRequest): MockResponse { val body = """ { "presenter": "Jackson Cheek", "twitter": "@jacksoncheek" } """.trimIndent().toByteArray() return MockResponse().apply { setResponseCode(200) setBody(Buffer().readFrom(body.inputStream())) } } }
  10. @jacksoncheek • Stub sad paths Examples class MockDispatcher : Dispatcher()

    { override fun dispatch(request: RecordedRequest): MockResponse { val body = """ { "message": "Login Required" } """.trimIndent().toByteArray() return MockResponse().apply { setResponseCode(401) setBody(Buffer().readFrom(body.inputStream())) } } }
  11. @jacksoncheek // Debug Dependencies dependencies { debugImplementation project(":example-debug-module") } //

    Release Dependencies dependencies { releaseImplementation project(“:example-no-op-module") } Application Breakdown
  12. @jacksoncheek // Debug Dependencies dependencies { debugImplementation project(":example-debug-module") } //

    Release Dependencies dependencies { releaseImplementation project(“:example-no-op-module") } Application Breakdown
  13. @jacksoncheek How do you design a library with all of

    your awesome debug tooling? But, What About SDKs?
  14. @jacksoncheek How do you design a library with all of

    your awesome debug tooling? But, What About SDKs? How do you avoid the riskiness of shipping them “turned off” in Production code?
  15. @jacksoncheek Common Approach /** Builder code to return your CustomSdk

    implementation */ class CustomSdkBuilder { /** Builds the CustomSdkImpl */ fun build(): CustomSdk { return CustomSdkImpl() } }
  16. @jacksoncheek Common Approach /** Builder code to return your CustomSdk

    implementation */ class CustomSdkBuilder { /** Builds the CustomSdkImpl */ fun build(): CustomSdk { return CustomSdkImpl( logger = logger ) } fun logger(logger: Logger) = apply { this.logger = logger } }
  17. @jacksoncheek Common Approach /** Builder code to return your CustomSdk

    implementation */ class CustomSdkBuilder { /** Builds the CustomSdkImpl */ fun build(): CustomSdk { return CustomSdkImpl( logger = logger ) } fun logger(logger: Logger) = apply { this.logger = logger } }
  18. @jacksoncheek Common Approach /** Builder code to return your CustomSdk

    implementation */ class CustomSdkBuilder { /** Builds the CustomSdkImpl */ fun build(): CustomSdk { return CustomSdkImpl( logger = logger // will be NoOpLogger in Release builds ) } fun logger(logger: Logger) = apply { if (isDebuggable) { this.logger = logger } else { this.logger = NoOpLogger() } } // BAD!!! private val isDebuggable: Boolean = if (BuildConfig.DEBUG) true else false }
  19. @jacksoncheek Common Approach /** Builder code to return your CustomSdk

    implementation */ class CustomSdkBuilder { /** Builds the CustomSdkImpl */ fun build(): CustomSdk { return CustomSdkImpl( logger = logger // will be NoOpLogger in Release builds ) } fun logger(logger: Logger) = apply { if (isDebuggable) { this.logger = logger } else { this.logger = NoOpLogger() } } // BAD!!! private val isDebuggable: Boolean = if (BuildConfig.DEBUG) true else false }
  20. @jacksoncheek “The code paths aren't being executed in Release. Just

    let ProGuard’s code optimization strip it out…” Why Is This Risky?
  21. @jacksoncheek “The code paths aren't being executed in Release. Just

    let ProGuard’s code optimization strip it out…” Why Is This Risky? How confident are you in your release pipeline build steps?
  22. @jacksoncheek Modular Builders Separate your Debug features and your Production

    features by writing two SDK builders (Dev and Prod).
  23. @jacksoncheek Modular Builders Separate your Debug features and your Production

    features by writing two SDK builders (Dev and Prod). Only the Prod builder is published with the final Android library artifacts.
  24. @jacksoncheek Modular Builders Separate your Debug features and your Production

    features by writing two SDK builders (Dev and Prod). Only the Prod builder is published with the final Android library artifacts. The Dev builder is only distributed with Debug flavors of your sample application.
  25. @jacksoncheek • Modify network endpoints • HTTP traffic interceptors •

    Custom logger injections • Mock web server • Stub happy and sad paths • Localization changes • Deep linking • Simulating push notifications • Alternate app resources • Inspecting analytics • Bug reporting • Plus more! ? ? ? ? ? SDK Debug Tooling
  26. @jacksoncheek • Modify network endpoints • HTTP traffic interceptors •

    Custom logger injections • Mock web server • Stub happy and sad paths • Localization changes • Deep linking • Simulating push notifications • Alternate app resources • Inspecting analytics • Bug reporting • Plus more! AW ESOME SDK Debug Tooling
  27. @jacksoncheek Build an Android SDK that fetches a user’s profile

    A Wild Scenario Appears! Acceptance Criteria: • Demo application to showcase the SDK
  28. @jacksoncheek Build an Android SDK that fetches a user’s profile

    A Wild Scenario Appears! Acceptance Criteria: • Demo application to showcase the SDK • Include a “locked” down build variant with no debug tooling
  29. @jacksoncheek Build an Android SDK that fetches a user’s profile

    A Wild Scenario Appears! Acceptance Criteria: • Demo application to showcase the SDK • Include a “locked” down build variant with no debug tooling • Include an “unlocked” build variant with awesome debug features
  30. @jacksoncheek Hello, My Name Is 1. Client application Hello launches.

    2. User clicks “Get Profile” button and the ViewModel launches our UserProfileSdk. 3. The SDK begins a new Activity and starts a ViewModel that performs our data fetching. 4. A user profile is retrieved from the API and is rendered on screen. 5. User clicks “Got It!” button, which exits the SDK and returns a Result object to Hello via a callback. 6. The client application renders the Result back to the user on the “Hello, My Name Is” ImageView.
  31. @jacksoncheek /** Public interface. */ interface UserProfileSdk { /** Begins

    the UserProfileSdk flow. * * @param callback invoked asynchronously after completion. */ fun startFlow( callback: ResultCallback ) } Starting Simple (Contract)
  32. @jacksoncheek /** Public interface. */ interface UserProfileSdk { /** Begins

    the UserProfileSdk flow. * * @param callback invoked asynchronously after completion. */ fun startFlow( callback: ResultCallback ) } typealias ResultCallback = (Result) -> Unit Starting Simple (Contract)
  33. @jacksoncheek internal class UserProfileSdkImpl(private val params: Params) : UserProfileSdk {

    // ... override fun startFlow( callback: ResultCallback ) { } } SDK Implementation
  34. @jacksoncheek internal class UserProfileSdkImpl(private val params: Params) : UserProfileSdk {

    // ... override fun startFlow( callback: ResultCallback ) { sdkSession.launch { uiSdk.startFlow(callback) } } } SDK Implementation
  35. @jacksoncheek class UserProfileSdkBuilder { private lateinit var params: Params fun

    params(params: Params) = apply { this.params = params } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { requireNotNull(params) { "`params` cannot be null" } return UserProfileSdkImpl( params = params ) } } SDK Builder
  36. @jacksoncheek class UserProfileSdkBuilder { private lateinit var params: Params fun

    params(params: Params) = apply { this.params = params } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { requireNotNull(params) { "`params` cannot be null" } return UserProfileSdkImpl( params = params ) } } SDK Builder
  37. @jacksoncheek class UserProfileSdkBuilder { private lateinit var params: Params fun

    params(params: Params) = apply { this.params = params } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { requireNotNull(params) { "`params` cannot be null" } return UserProfileSdkImpl( params = params ) } } SDK Builder
  38. @jacksoncheek class UserProfileSdkBuilder { private lateinit var params: Params fun

    params(params: Params) = apply { this.params = params } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { requireNotNull(params) { "`params` cannot be null" } return UserProfileSdkImpl( params = params ) } } SDK Builder
  39. @jacksoncheek class UserProfileSdkBuilder { private lateinit var params: Params fun

    params(params: Params) = apply { this.params = params } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { requireNotNull(params) { "`params` cannot be null" } return UserProfileSdkImpl( params = params ) } } SDK Builder
  40. @jacksoncheek class UserProfileSdkBuilder { fun baseUrl(baseUrl: BaseUrl) = apply {

    this.baseUrl = baseUrl } // ... } Let’s Start Adding Features
  41. @jacksoncheek class UserProfileSdkBuilder { fun baseUrl(baseUrl: BaseUrl) = apply {

    this.baseUrl = baseUrl } fun region(region: Region) = apply { this.region = region } // ... } Let’s Start Adding Features
  42. @jacksoncheek class UserProfileSdkBuilder { fun baseUrl(baseUrl: BaseUrl) = apply {

    this.baseUrl = baseUrl } fun region(region: Region) = apply { this.region = region } fun okHttpBuilder(okHttpBuilder: OkHttpClient.Builder) = apply { this.okHttpBuilder = okHttpBuilder } // ... } Let’s Start Adding Features
  43. @jacksoncheek class UserProfileSdkBuilder { fun baseUrl(baseUrl: BaseUrl) = apply {

    this.baseUrl = baseUrl } fun region(region: Region) = apply { this.region = region } fun okHttpBuilder(okHttpBuilder: OkHttpClient.Builder) = apply { this.okHttpBuilder = okHttpBuilder } fun logger(logger: Logger) = apply { this.logger = logger } // ... } Let’s Start Adding Features
  44. @jacksoncheek Modular SDK Builder Design Demo Application Prod SDK Builder

    Dev SDK Builder Dev Flavors Injecting Debug Features Prod Flavors Locked Down Release Features
  45. @jacksoncheek Modular SDK Builder Design Prod SDK Builder Dev SDK

    Builder UserProfileSdk Common SDK Impl Demo Application
  46. @jacksoncheek Modular SDK Builder Design Core Library UI Library UserProfileSdk

    Base Libraries Prod SDK Builder Dev SDK Builder Demo Application
  47. @jacksoncheek Modular SDK Builder Design Demo Application Prod SDK Builder

    Dev SDK Builder Core Library UI Library UserProfileSdk
  48. @jacksoncheek // LeakCanary dependencies { debugImplementation "com.squareup.leakcanary:leakcanary-android:1.3.1" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:1.3.1" }

    // Chuck dependencies { debugCompile "com.readystatesoftware.chuck:library:1.1.0" releaseCompile "com.readystatesoftware.chuck:library-no-op:1.1.0" } Existing Examples
  49. @jacksoncheek // LeakCanary dependencies { debugImplementation "com.squareup.leakcanary:leakcanary-android:1.3.1" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:1.3.1" }

    // Chuck dependencies { debugCompile "com.readystatesoftware.chuck:library:1.1.0" releaseCompile "com.readystatesoftware.chuck:library-no-op:1.1.0" } Existing Examples
  50. @jacksoncheek // LeakCanary dependencies { debugImplementation "com.squareup.leakcanary:leakcanary-android:1.3.1" releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:1.3.1" }

    // Chuck dependencies { debugCompile "com.readystatesoftware.chuck:library:1.1.0" releaseCompile "com.readystatesoftware.chuck:library-no-op:1.1.0" } Existing Examples
  51. @jacksoncheek /** Prod SDK Builder */ class UserProfileSdkBuilder { fun

    appContext(appContext: Context) = apply { this.appContext = appContext } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { // Return the UserProfileSdkImpl } } Prod SDK Builder
  52. @jacksoncheek /** Prod SDK Builder */ class UserProfileSdkBuilder { fun

    appContext(appContext: Context) = apply { this.appContext = appContext } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { // Return the UserProfileSdkImpl } } Prod SDK Builder
  53. @jacksoncheek /** Dev SDK Builder */ class UserProfileSdkBuilder { fun

    appContext(appContext: Context) = apply { this.appContext = appContext } fun baseUrl(baseUrl: BaseUrl) = apply { this.baseUrl = baseUrl } fun region(region: Region) = apply { this.region = region } fun okHttpBuilder(okHttpBuilder: OkHttpClient.Builder) = apply { this.okHttpBuilder = okHttpBuilder } fun logger(logger: Logger) = apply { this.logger = logger } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { // Return the UserProfileSdkImpl } } Dev SDK Builder
  54. @jacksoncheek /** Dev SDK Builder */ class UserProfileSdkBuilder { fun

    appContext(appContext: Context) = apply { this.appContext = appContext } fun baseUrl(baseUrl: BaseUrl) = apply { this.baseUrl = baseUrl } fun region(region: Region) = apply { this.region = region } fun okHttpBuilder(okHttpBuilder: OkHttpClient.Builder) = apply { this.okHttpBuilder = okHttpBuilder } fun logger(logger: Logger) = apply { this.logger = logger } /** Builds the UserProfileSdkImpl. */ fun build(): UserProfileSdk { // Return the UserProfileSdkImpl } } Dev SDK Builder
  55. @jacksoncheek • Framework-less dependency injection • SDK instance scoped to

    the MainApplication class Demo App + SDK Integration
  56. @jacksoncheek • Framework-less dependency injection • SDK instance scoped to

    the MainApplication class • Product flavors for separating debug / release dependencies Demo App + SDK Integration
  57. @jacksoncheek Injecting the SDK class MainApplication : Application() { val

    sdk: UserProfileSdk by lazy { UserProfileSdkBuilder() .appContext(this) .build() } override fun onCreate() { super.onCreate() // Initialize the UserProfileSdk before onCreate() finishes sdk } }
  58. @jacksoncheek Injecting the SDK class MainApplication : Application() { val

    sdk: UserProfileSdk by lazy { UserProfileSdkBuilder() .appContext(this) .build() } override fun onCreate() { super.onCreate() // Initialize the UserProfileSdk before onCreate() finishes sdk } } Which Builder?
  59. @jacksoncheek Launch the SDK class MainActivity : AppCompatActivity() { val

    app get() = application as MainApplication val sdk: UserProfileSdk get() = app.sdk override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.sample_activity_main) sdk.startFlow { result -> println("UserProfileSdk Result: $result") } } }
  60. @jacksoncheek Flavor Dimensions android { flavorDimensions "CONFIGURABLE" productFlavors { dev

    { dimension "CONFIGURABLE" applicationIdSuffix ".dev" } mock { dimension "CONFIGURABLE" applicationIdSuffix ".mock" } prod { dimension "CONFIGURABLE" } } }
  61. @jacksoncheek Flavors Dependencies // Dev Dependencies dependencies { devImplementation project(":user-profile-sdk-builder-dev")

    } // Mock Dependencies dependencies { mockImplementation project(":user-profile-sdk-builder-dev") } // Prod Dependencies dependencies { prodImplementation project(":user-profile-sdk-builder-prod") } // Common Dependencies dependencies { // Everything else }
  62. @jacksoncheek Flavors Dependencies // Dev Dependencies dependencies { devImplementation project(":user-profile-sdk-builder-dev")

    } // Mock Dependencies dependencies { mockImplementation project(":user-profile-sdk-builder-dev") } // Prod Dependencies dependencies { prodImplementation project(":user-profile-sdk-builder-prod") } // Common Dependencies dependencies { // Everything else }
  63. @jacksoncheek DI Object Graph (Mock) /** A graph for bindings

    specific to the MOCK flavor. */ interface ConfigurableGraph { val baseUrl: BaseUrl val okHttpBuilder: OkHttpClient.Builder val userProfileSdk: UserProfileSdk }
  64. @jacksoncheek DI Object Graph (Mock) /** A graph for bindings

    specific to the MOCK flavor. */ interface ConfigurableGraph { val baseUrl: BaseUrl val okHttpBuilder: OkHttpClient.Builder val userProfileSdk: UserProfileSdk }
  65. @jacksoncheek DI Object Graph (Mock) /** A graph for bindings

    specific to the MOCK flavor. */ interface ConfigurableGraph { val baseUrl: BaseUrl val okHttpBuilder: OkHttpClient.Builder val userProfileSdk: UserProfileSdk }
  66. @jacksoncheek DI Object Graph (Mock) /** A graph for bindings

    specific to the MOCK flavor. */ interface ConfigurableGraph { val baseUrl: BaseUrl val okHttpBuilder: OkHttpClient.Builder val userProfileSdk: UserProfileSdk }
  67. @jacksoncheek DI Object Graph (Mock) import com.jacksoncheek.userprofile.builder.dev.UserProfileSdkBuilder /** A graph

    for bindings specific to the MOCK flavor. */ class ConfigurableGraphImpl(val appContext: Context) : ConfigurableGraph { override val baseUrl by lazy { "http://localhost:8080" } override val okHttpBuilder by lazy { OkHttpClient.Builder() .addInterceptor(ChuckInterceptor(appContext).showNotification(true)) } override val userProfileSdk by lazy { UserProfileSdkBuilder() .appContext(appContext) .baseUrl(baseUrl) .okHttpBuilder(okHttpBuilder) .build() } }
  68. @jacksoncheek DI Object Graph (Mock) import com.jacksoncheek.userprofile.builder.dev.UserProfileSdkBuilder /** A graph

    for bindings specific to the MOCK flavor. */ class ConfigurableGraphImpl(val appContext: Context) : ConfigurableGraph { override val baseUrl by lazy { "http://localhost:8080" } override val okHttpBuilder by lazy { OkHttpClient.Builder() .addInterceptor(ChuckInterceptor(appContext).showNotification(true)) } override val userProfileSdk by lazy { UserProfileSdkBuilder() .appContext(appContext) .baseUrl(baseUrl) .okHttpBuilder(okHttpBuilder) .build() } }
  69. @jacksoncheek DI Object Graph (Mock) import com.jacksoncheek.userprofile.builder.dev.UserProfileSdkBuilder /** A graph

    for bindings specific to the MOCK flavor. */ class ConfigurableGraphImpl(val appContext: Context) : ConfigurableGraph { override val baseUrl by lazy { "http://localhost:8080" } override val okHttpBuilder by lazy { OkHttpClient.Builder() .addInterceptor(ChuckInterceptor(appContext).showNotification(true)) } override val userProfileSdk by lazy { UserProfileSdkBuilder() .appContext(appContext) .baseUrl(baseUrl) .okHttpBuilder(okHttpBuilder) .build() } } Dev SDK Builder
  70. @jacksoncheek DI Object Graph (Mock) import com.jacksoncheek.userprofile.builder.dev.UserProfileSdkBuilder /** A graph

    for bindings specific to the MOCK flavor. */ class ConfigurableGraphImpl(val appContext: Context) : ConfigurableGraph { override val baseUrl by lazy { "http://localhost:8080" } override val okHttpBuilder by lazy { ... } override val userProfileSdk by lazy { ... } companion object { fun create(appContext: Context): ConfigurableGraph { return ConfigurableGraph.Impl(appContext) } } }
  71. @jacksoncheek DI Object Graph (Prod) import com.jacksoncheek.userprofile.builder.prod.UserProfileSdkBuilder /** A graph

    for bindings specific to the PROD flavor. */ interface ConfigurableGraph { val userProfileSdk: UserProfileSdk // And NO debug tooling features } class ConfigurableGraphImpl(val appContext: Context) : ConfigurableGraph { override val userProfileSdk by lazy { UserProfileSdkBuilder() .appContext(appContext) .build() } }
  72. @jacksoncheek DI Object Graph (Prod) import com.jacksoncheek.userprofile.builder.prod.UserProfileSdkBuilder /** A graph

    for bindings specific to the PROD flavor. */ interface ConfigurableGraph { val userProfileSdk: UserProfileSdk // And NO debug tooling features } class ConfigurableGraphImpl(val appContext: Context) : ConfigurableGraph { override val userProfileSdk by lazy { UserProfileSdkBuilder() .appContext(appContext) .build() } } Prod SDK Builder
  73. @jacksoncheek • Extension functions for the MainApplication class Flavor Dimensions

    /** Add different functionality to the `CONFIGURABLE` flavor dimension. */ fun MainApplication.onCreateConfigurableFlavorDimension(): ConfigurableGraph { // Initialize and return the MOCK DI object graph return ConfigurableGraph.create(this) }
  74. @jacksoncheek • Extension functions for the MainApplication class Flavor Dimensions

    /** Add different functionality to the `CONFIGURABLE` flavor dimension. */ fun MainApplication.onCreateConfigurableFlavorDimension(): ConfigurableGraph { // Initialize and return the MOCK DI object graph return ConfigurableGraph.create(this) }
  75. @jacksoncheek Flavors Source Structure \--- :user-profile-sdk-sample-app (demo application) +--- src

    | +--- dev | | \--- java | | \--- com.jacksoncheek.userprofile.sample | | +--- ConfigurableDimension.kt | | \--- ConfigurableGraph.kt | +--- main | | \--- java | | \--- com.jacksoncheek.userprofile.sample | | +--- Graph.kt | | +--- MainActivity.kt | | \--- MainApplication.kt | +--- mock | | \--- java | | \--- com.jacksoncheek.userprofile.sample | | +--- ConfigurableDimension.kt | | \--- ConfigurableGraph.kt | +--- prod | \--- java | \--- com.jacksoncheek.userprofile.sample | +--- ConfigurableDimension.kt | \--- ConfigurableGraph.kt \--- build.gradle
  76. @jacksoncheek Flavors Source Structure \--- :user-profile-sdk-sample-app (demo application) +--- src

    | +--- dev | | \--- java | | \--- com.jacksoncheek.userprofile.sample | | +--- ConfigurableDimension.kt | | \--- ConfigurableGraph.kt | +--- main | | \--- java | | \--- com.jacksoncheek.userprofile.sample | | +--- Graph.kt | | +--- MainActivity.kt | | \--- MainApplication.kt | +--- mock | | \--- java | | \--- com.jacksoncheek.userprofile.sample | | +--- ConfigurableDimension.kt | | \--- ConfigurableGraph.kt | +--- prod | \--- java | \--- com.jacksoncheek.userprofile.sample | +--- ConfigurableDimension.kt | \--- ConfigurableGraph.kt \--- build.gradle
  77. @jacksoncheek Injecting the SDK via Graph class MainApplication : Application()

    { val configurableGraph: ConfigurableGraph by lazy { onCreateConfigurableFlavorDimension() } override fun onCreate() { super.onCreate() // Initialize the configurable graph before onCreate() finishes configurableGraph } }
  78. @jacksoncheek Injecting the SDK via Graph class MainApplication : Application()

    { val configurableGraph: ConfigurableGraph by lazy { onCreateConfigurableFlavorDimension() } override fun onCreate() { super.onCreate() // Initialize the configurable graph before onCreate() finishes configurableGraph } }
  79. @jacksoncheek Launch the SDK via Graph class MainActivity : AppCompatActivity()

    { val app get() = application as MainApplication val configurableGraph: ConfigurableGraph get() = app.configurableGraph override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.sample_activity_main) configurableGraph.userProfileSdk.startFlow { result -> println("UserProfileSdk Result: $result") } } }
  80. @jacksoncheek Launch the SDK via Graph class MainActivity : AppCompatActivity()

    { val app get() = application as MainApplication val configurableGraph: ConfigurableGraph get() = app.configurableGraph override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.sample_activity_main) configurableGraph.userProfileSdk.startFlow { result -> println("UserProfileSdk Result: $result") } } }
  81. @jacksoncheek • Leveraging modular SDK builders to securely separate debug

    features from release builds. • Dev SDK builder artifact is NOT published. Recap
  82. @jacksoncheek • Leveraging modular SDK builders to securely separate debug

    features from release builds. • Dev SDK builder artifact is NOT published. • Debug / testing code is not shipped in production code. Recap
  83. @jacksoncheek • Leveraging modular SDK builders to securely separate debug

    features from release builds. • Dev SDK builder artifact is NOT published. • Debug / testing code is not shipped in production code. • Product Owners and other stakeholders can happily continue using the debug features they’re accustomed to . Recap
  84. @jacksoncheek • Leveraging modular SDK builders to securely separate debug

    features from release builds. • Dev SDK builder artifact is NOT published. • Debug / testing code is not shipped in production code. • Product Owners and other stakeholders can happily continue using the debug features they’re accustomed to . • Devs can stay happy that their SDK design is protected! Recap
  85. @jacksoncheek Awesome Android SDK Design And Keeping Your Product Owners

    Happy With Debug Features! #AndroidSummit Questions? Jackson Cheek @jacksoncheek