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

The definitive guide to Android library development

Jeroen Mols
October 20, 2021

The definitive guide to Android library development

Video: https://www.droidcon.com/2021/11/10/the-definitive-guide-to-android-library-development/

Android library/SDK development unfortunately is harder than it should be. This is caused by inferior tooling (Why do you need fat AAR?), missing documentation (What causes transitive dependency conflicts?), and different architecture considerations (Should you modularize your SDK?).

Having switched to full-time Android SDK development two years ago, I'm summarizing my lessons learned into a talk I wish would have existed when I got started.

⚠️ This talk is aimed at anyone curious to level up their knowledge of the Android ecosystem, not just at beginning/seasoned SDK developers!

---------------------------

Further reads:
- Maintaining compatibility in Kotlin libraries by Márton Braun
- The hidden Kotlin gem: Deprecations with ReplaceWith by Marc Reichelt
- Publishing libraries to Maven Central in 2021 by Marton Braun
- Java interoperability policy for major version updates by Jake Wharton
- Public API challenges in Kotlin by Jake Wharton
- How to publish and distribute your Android library by Marco Gomiero
- What is a diamond dependency conflict by Google
- All about Opt-in annotationsby Márton Braun

Jeroen Mols

October 20, 2021
Tweet

More Decks by Jeroen Mols

Other Decks in Programming

Transcript

  1. @MOLSJEROEN
    THE DEFINITIVE GUIDE
    TO ANDROID LIBRARY
    DEVELOPMENT

    View full-size slide

  2. @MOLSJEROEN
    GOAL


    OUR FIRST LIBRARY


    TRANSITIVE DEPENDENCIES


    MODULARIZATION


    DEPENDENCY CONFLICTS


    TIPS & TOOLS

    View full-size slide

  3. @MOLSJEROEN
    TERMINOLOGY
    • Software Development Kit (SDK)


    • Android library

    View full-size slide

  4. @MOLSJEROEN
    ANDROID LIBRARIES
    APP

    View full-size slide

  5. @MOLSJEROEN
    ANDROID LIBRARIES
    APP
    COMPOSE OKHTTP …

    View full-size slide

  6. @MOLSJEROEN
    ANDROID LIBRARIES
    APP
    COMPOSE OKHTTP …
    MAVEN

    View full-size slide

  7. @MOLSJEROEN
    ANDROID LIBRARIES
    COMPOSE OKHTTP …
    MAVEN
    APP
    MODULE 1 MODULE 2

    View full-size slide

  8. @MOLSJEROEN
    ANDROID LIBRARIES
    COMPOSE OKHTTP LIBRARY
    MAVEN
    APP
    MODULE 1 MODULE 2

    View full-size slide

  9. @MOLSJEROEN
    ANDROID LIBRARIES
    dependencies {
    implementation "com.jeroenmols:library:1.0.0"
    }

    View full-size slide

  10. OUR FIRST LIBRARY

    View full-size slide

  11. ANDROID STUDIO TO THE
    RESCUE!

    View full-size slide

  12. TOOLING FOR ANDROID
    LIBRARIES IS LACKING

    View full-size slide

  13. @MOLSJEROEN
    ANDROID STUDIO PROJECT
    PROJECT APP

    View full-size slide

  14. @MOLSJEROEN
    ANDROID STUDIO PROJECT
    LIBRARY
    APP
    PROJECT

    View full-size slide

  15. @MOLSJEROEN
    RUN & TEST
    $./gradlew :app:installDebu
    g

    $ ./gradlew :library:testDebugUnitTes
    t

    $ ./gradlew :library:connectedAndroidTest

    View full-size slide

  16. @MOLSJEROEN
    BUILD
    $ ./gradlew :library:assembleRelease

    View full-size slide

  17. @MOLSJEROEN
    APK


    •Compiled code


    •Processed resources


    •Android manifest


    •Compiled transitive dependencies


    •Can be run on Android device


    •Signed
    JAR


    •Compiled code


    •Cannot be run on Android device


    •Unsigned

    View full-size slide

  18. @MOLSJEROEN
    APK


    •Compiled code


    •Processed resources


    •Android manifest


    •Compiled transitive dependencies


    •Can be run on Android device


    •Signed
    AAR


    •Compiled code


    •Processed resources


    •Android manifest


    •Cannot be run on Android device


    •Unsigned

    View full-size slide

  19. @MOLSJEROEN
    DISTRIBUTE - STEPS
    1.Build a release (see before)


    2.Generate and publish PGP key


    3.Sign build


    4.Upload to Maven central


    => Tutorial by Márton Braun:

    https://getstream.io/blog/publishing-libraries-to-mavencentral-2021/

    View full-size slide

  20. TOOLING FOR ANDROID
    LIBRARIES IS LACKING

    View full-size slide

  21. TRANSITIVE DEPENDENCIES

    View full-size slide

  22. @MOLSJEROEN
    EXTERNAL DEPENDENCIES
    LIBRARY
    APP
    PROJECT
    MAVEN OKHTTP

    View full-size slide

  23. @MOLSJEROEN
    EXTERNAL DEPENDENCIES
    PROJECT
    MAVEN
    CUSTOMER APP
    LIBRARY

    View full-size slide

  24. @MOLSJEROEN
    EXTERNAL DEPENDENCIES
    PROJECT
    MAVEN OKHTTP
    CUSTOMER APP
    LIBRARY

    View full-size slide

  25. @MOLSJEROEN
    EXTERNAL DEPENDENCIES
    dependencies {
    implementation "com.jeroenmols:library:1.0.0"
    implementation "com.squareup.okhttp3:okhttp:4.9.2"
    }

    View full-size slide

  26. @MOLSJEROEN
    TRANSITIVE DEPENDENCIES
    dependencies {
    implementation "com.jeroenmols:library:1.0.0"
    }

    View full-size slide

  27. @MOLSJEROEN
    EXTERNAL DEPENDENCIES
    PROJECT
    MAVEN
    OKHTTP
    CUSTOMER APP
    LIBRARY

    View full-size slide

  28. @MOLSJEROEN
    POM - XML FILE

    com.jeroenmols
    library
    1.0.0


    com.squareup.okhttp3
    okhttp3
    4.9.0



    View full-size slide

  29. @MOLSJEROEN
    POM - XML FILE
    >

    com.jeroenmols
    library
    1.0.0
    >

    >

    com.squareup.okhttp3>

    okhttp3>

    4.9.0>

    >

    >


    View full-size slide

  30. @MOLSJEROEN
    POM - XML FILE
    >

    com.jeroenmols>

    library>

    1.0.0>



    com.squareup.okhttp3
    okhttp3
    4.9.0

    >


    View full-size slide

  31. ALWAYS TEST LIBRARIES
    THROUGH MAVEN

    View full-size slide

  32. MODULARIZATION

    View full-size slide

  33. @MOLSJEROEN
    APP MODULARISATION
    APP
    FEATURE 2
    FEATURE 3
    FEATURE 4
    FEATURE 5
    FEATURE 6
    CORE 4 CORE 4
    CORE 2 CORE 3
    FEATURE 1
    CORE 1

    View full-size slide

  34. @MOLSJEROEN
    LIBRARY MODULARIZATION
    LIBRARY
    APP
    PROJECT
    DATABASE UI-COMPONENTS

    View full-size slide

  35. @MOLSJEROEN
    TEST SETUP
    // app
    Library().initialize()
    // library
    class Library {
    fun initialize() = Database().initialize()
    }

    // database
    class Database {
    fun initialize() = System.out.println(“database ready")
    }

    View full-size slide

  36. PROBLEM #1

    SUBMODULES NOT INCLUDED IN AAR

    View full-size slide

  37. @MOLSJEROEN
    LIBRARY SUBMODULE TEST
    ./gradlew :library:assembleRelease

    View full-size slide

  38. @MOLSJEROEN
    LIBRARY SUBMODULE TEST
    ./gradlew :library:assembleReleas
    e

    // App build.gradl
    e

    dependencies {
    // implementation project(':library')
    implementation
    fi
    les('../library/build/outputs/aar/library-release.aar')
    ...
    }

    View full-size slide

  39. TOOLING FOR ANDROID
    LIBRARIES IS LACKING

    View full-size slide

  40. @MOLSJEROEN
    SUBMODULES NOT INCLUDED IN AAR
    1. Release submodules to Maven


    2. Fat AAR


    3. Single module SDK

    View full-size slide

  41. @MOLSJEROEN
    1. RELEASE SUBMODULES TO MAVEN
    COMPOSE LIBRARY
    MAVEN
    APP
    UI-COMPONENTS DATABASE

    View full-size slide

  42. @MOLSJEROEN
    1. RELEASE SUBMODULES TO MAVEN
    // Library build.gradl
    e

    dependencies {
    implementation "com.jeroenmols:database:1.0.0"
    implementation "com.jeroenmols:ui-components:1.0.0"
    }

    View full-size slide

  43. @MOLSJEROEN
    2. FAT AAR
    apply plugin: 'com.kezong.fat-aar'
    ...
    dependencies {
    embed project(path: ':modules:database', con
    fi
    guration:'default')
    embed project(path: ':modules:ui-components', con
    fi
    guration:'default')
    }

    View full-size slide

  44. @MOLSJEROEN
    3. SINGLE MODULE SDK
    LIBRARY
    APP
    PROJECT

    View full-size slide

  45. PROBLEM #2

    PUBLIC INTERFACE SUBMODULES EXPOSED

    View full-size slide

  46. @MOLSJEROEN
    KOTLIN VISIBILITY MODIFIERS
    • private: visible inside this class only


    • protected: same as private + visible in subclasses too


    • internal: visible to all classes inside this module


    • public: visible to all classes

    View full-size slide

  47. @MOLSJEROEN
    1

    View full-size slide

  48. @MOLSJEROEN
    PUBLIC INTERFACE SUBMODULES EXPOSED
    1. Internal package


    2. Obfuscate non-public classes


    3. Single module SDK

    View full-size slide

  49. @MOLSJEROEN
    1. INTERNAL PACKAGE
    package com.jeroenmols.internal.database

    View full-size slide

  50. @MOLSJEROEN
    2. OBFUSCATE NON-PUBLIC CLASSES
    package a.a.a

    View full-size slide

  51. @MOLSJEROEN
    2. OBFUSCATE NON-PUBLIC CLASSES
    // proguard-rules.pr
    o

    repackageclasses com.jeroenmols.internal



    package com.jeroenmols.internal.a

    View full-size slide

  52. @MOLSJEROEN
    3. SINGLE MODULE SDK
    LIBRARY
    APP
    PROJECT

    View full-size slide

  53. RECOMMENDATION


    LIBRARY MODULARIZATION

    View full-size slide

  54. @MOLSJEROEN
    MODULARIZATION RECOMMENDATION
    • Modularizing SDKs is tedious


    • Single module for small and mid sized SDKs


    • Spin off reusable maven artefacts for larger SDKs


    • Reduce public API surface


    • Tooling is lacking

    View full-size slide

  55. TRANSITIVE DEPENDENCY
    CONFLICTS

    View full-size slide

  56. @MOLSJEROEN
    TRANSITIVE DEPENDENCY CONFLICTS
    PROJECT
    MAVEN
    TRANSITIVE DEPENDENCY 1
    CUSTOMER APP
    ANOTHER LIBRARY

    View full-size slide

  57. @MOLSJEROEN
    TRANSITIVE DEPENDENCY CONFLICTS
    PROJECT
    MAVEN
    TRANSITIVE DEPENDENCY 1
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    TRANSITIVE
    DEPENDENCY 2
    TRANSITIVE
    DEPENDENCY 3

    View full-size slide

  58. @MOLSJEROEN
    TRANSITIVE DEPENDENCY CONFLICTS
    1. con
    fl
    icting transitive dependency versions


    2. incompatible transitive dependencies

    View full-size slide

  59. PROBLEM #1


    TRANSITIVE DEPENDENCY CONFLICTS

    View full-size slide

  60. @MOLSJEROEN
    CONFLICTING DEPENDENCY VERSIONS
    dependencies {
    implementation "com.jeroenmols:library:1.0.0"
    implementation "com.example:anotherlibrary:2.0.0"
    }

    View full-size slide

  61. @MOLSJEROEN
    TRANSITIVE DEPENDENCY CONFLICTS
    PROJECT
    MAVEN
    OKHTTP V3
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    OKHTTP V4

    View full-size slide

  62. @MOLSJEROEN
    TRANSITIVE DEPENDENCY CONFLICTS
    PROJECT
    MAVEN
    OKHTTP V3
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    OKHTTP V4

    View full-size slide

  63. @MOLSJEROEN
    CONFLICT RESOLUTION
    1.Force dependency resolution in CustomerApp


    2.Loosen dependency requirements in library


    3.Remove transitive dependency from library

    View full-size slide

  64. @MOLSJEROEN
    1. FORCE DEPENDENCY IN CUSTOMERAPP
    implementation('com.jeroenmols:library:1.0.0') {
    exclude group: 'com.squareup.okhttp3', module: 'okhttp'
    }

    View full-size slide

  65. @MOLSJEROEN
    1. FORCE DEPENDENCY IN CUSTOMERAPP
    con
    fi
    gurations.all
    {

    resolutionStrategy {
    force 'com.squareup.okhttp3:okhttp:3.12.0'
    }
    }

    View full-size slide

  66. @MOLSJEROEN
    1. FORCE DEPENDENCY IN CUSTOMERAPP
    • No library update required


    • Force untested combinations of dependencies


    • Burden for SDK customers

    View full-size slide

  67. @MOLSJEROEN
    CASE STUDY: BREAKING API CHANGES
    PROJECT
    MAVEN
    OKHTTP V2
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    OKHTTP V3

    View full-size slide

  68. @MOLSJEROEN
    OkHtttp v2


    OkHttp v3

    View full-size slide

  69. @MOLSJEROEN
    CASE STUDY: BREAKING API CHANGES
    package com.square.okhttp
    3

    implementation ‘com.squareup.okhttp3:okhttp3:4.9.0'

    View full-size slide

  70. PROBLEM #2


    INCOMPATIBLE TRANSITIVE DEPENDENCIES

    View full-size slide

  71. @MOLSJEROEN
    INCOMPATIBLE TRANSITIVE DEPENDENCIES
    PROJECT
    MAVEN
    PROTOBUF-JAVA
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    PROTOBUF-JAVALITE

    View full-size slide

  72. @MOLSJEROEN
    INCOMPATIBLE TRANSITIVE DEPENDENCIES
    PROJECT
    MAVEN
    PROTOBUF-JAVA
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    PROTOBUF-JAVALITE

    View full-size slide

  73. @MOLSJEROEN
    INCOMPATIBLE TRANSITIVE DEPENDENCIES
    $ ./gradlew clean assembleDebug


    > Task :app:checkDebugDuplicateClasses FAILED


    FAILURE: Build failed with an exception.


    * What went wrong:


    Execution failed for task ':myproject:checkReleaseDuplicateClasses'.


    > 1 exception was raised by workers:


    java.lang.RuntimeException: Duplicate class com.google.protobuf.AbstractMessageLite found in modules protobuf-java-3.11.1.jar
    (com.google.protobuf:protobuf-java:3.11.1) and protobuf-javalite-3.11.0.jar (com.google.protobuf:protobuf-javalite:3.11.0)


    Duplicate class com.google.protobuf.AbstractMessageLite$Builder found in modules protobuf-java-3.11.1.jar
    (com.google.protobuf:protobuf-java:3.11.1) and protobuf-javalite-3.11.0.jar (com.google.protobuf:protobuf-javalite:3.11.0)


    Duplicate class com.google.protobuf.AbstractMessageLite$Builder$LimitedInputStream found in modules protobuf-java-3.11.1.jar
    (com.google.protobuf:protobuf-java:3.11.1) and protobuf-javalite-3.11.0.jar (com.google.protobuf:protobuf-javalite:3.11.0)


    ...


    View full-size slide

  74. @MOLSJEROEN
    INCOMPATIBLE TRANSITIVE DEPENDENCIES
    +--- com.google.
    fi
    rebase:
    fi
    rebase-perf:19.0.7
    | +--- com.google.
    fi
    rebase:
    fi
    rebase-con
    fi
    g:19.0.4
    | | +--- com.google.
    fi
    rebase:
    fi
    rebase-abt:19.0.0
    | | | \--- com.google.protobuf:protobuf-lite:3.0.1

    View full-size slide

  75. @MOLSJEROEN
    INCOMPATIBILITY RESOLUTION
    1.Substitute dependency in CustomerApp


    2.Remove dependency from transitive dependency of library


    3.Remove transitive dependency from library

    View full-size slide

  76. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    PROJECT
    MAVEN
    PROTOBUF-JAVALITE
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    PBANDK
    PROTOBUF-JAVA

    View full-size slide

  77. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    PROJECT
    MAVEN
    PROTOBUF-JAVALITE
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    PBANDK
    PROTOBUF-JAVA

    View full-size slide

  78. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    ..
    .


    pro.streem.pbandk>

    pbandk-runtime-jvm
    0.9.0


    com.google.protobuf
    protobuf-java



    View full-size slide

  79. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    ..
    .

    >

    pro.streem.pbandk>

    pbandk-runtime-jvm>

    0.9.0>



    com.google.protobuf
    protobuf-java

    >


    View full-size slide

  80. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    con
    fi
    gurations.getByName(“releaseCompileClasspath").getResolvedCon
    fi
    guration(
    )

    .getFirstLevelModuleDependencies().each {
    ...
    def dependency = dependencies.appendNode(‘dependency’
    )

    if (it.moduleName.contains("pbandk")) {
    def exclusions = dependency.appendNode(‘exclusions'
    )

    def protobufExclusion = exclusions.appendNode('exclusion')
    protobufExclusion.appendNode('groupId', "com.google.protobuf")
    protobufExclusion.appendNode('artifactId', "protobuf-java")
    } } }

    View full-size slide

  81. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    pom.withXml
    {

    def dependencies = asNode().appendNode('dependencies'
    )

    con
    fi
    gurations.getByName(“releaseCompileClasspath").getResolvedCon
    fi
    guration(
    )

    .getFirstLevelModuleDependencies().each
    {

    ..
    .

    def dependency = dependencies.appendNode(‘dependency’
    )

    if (it.moduleName.contains("pbandk"))
    {

    def exclusions = dependency.appendNode(‘exclusions'
    )

    def protobufExclusion = exclusions.appendNode('exclusion')
    protobufExclusion.appendNode('groupId', "com.google.protobuf")
    protobufExclusion.appendNode('artifactId', "protobuf-java")
    } } }

    View full-size slide

  82. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    PROJECT
    MAVEN
    PROTOBUF-JAVALITE
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    PBANDK

    View full-size slide

  83. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    PROJECT
    MAVEN
    CUSTOMER APP
    LIBRARY
    PBANDK

    View full-size slide

  84. @MOLSJEROEN
    2. REMOVE FROM TRANSITIVE DEPENDENCY
    PROJECT
    MAVEN
    PROTOBUF-JAVALITE
    CUSTOMER APP
    ANOTHER LIBRARY LIBRARY
    PBANDK PROTOBUF-
    JAVALITE

    View full-size slide

  85. RECOMMENDATION


    TRANSITIVE DEPENDENCIES

    View full-size slide

  86. @MOLSJEROEN
    TRANSITIVE DEPENDENCY RECOMMENDATION
    1. Minimize transitive dependencies


    2. Use stable versions


    3. Use versions without known vulnerabilities


    4. Don’t use latest dependency versions


    5. Prefer exact versions over ranges


    6. Only use libraries that handle breaking changes


    => Put exceptions in release notes

    View full-size slide

  87. @MOLSJEROEN
    INVESTIGATE DEPENDENCY CONFLICTS
    ./gradlew :library:dependencie
    s

    $ ./gradlew --console plain :app:dependencies --con
    fi
    guration releaseRuntimeClasspath


    > Task :app:dependencies


    ------------------------------------------------------------


    Project :app


    ------------------------------------------------------------


    releaseRuntimeClasspath - Runtime classpath of compilation 'release' (target (androidJvm)).


    +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72


    | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72


    | \--- org.jetbrains:annotations:13.0


    +--- androidx.core:core-ktx:1.3.2


    | +--- org.jetbrains.kotlin:kotlin-stdlib:1.3.71 -> 1.3.72 (*)


    | +--- androidx.annotation:annotation:1.1.0


    | \--- androidx.core:core:1.3.2


    | \--- ...


    +--- androidx.appcompat:appcompat:1.2.0


    | +--- androidx.annotation:annotation:1.1.0




    View full-size slide

  88. LAST SDK INTEGRATED WILL
    GET THE BLAME

    View full-size slide

  89. TIPS & TOOLS

    View full-size slide

  90. @MOLSJEROEN
    2. LIBRARY RESOURCES

    View full-size slide

  91. @MOLSJEROEN
    2. LIBRARY RESOURCES - HIDE





    View full-size slide

  92. @MOLSJEROEN
    2. LIBRARY RESOURCES - UNIQUE NAME
    // library/build.gradle
    android {
    ...
    resourcePre
    fi
    x 'mylib_'
    }

    View full-size slide

  93. @MOLSJEROEN
    3. COMPATIBILITY: SOURCE
    PROJECT
    MAVEN
    CUSTOMER APP
    LIBRARY V1

    View full-size slide

  94. @MOLSJEROEN
    3. COMPATIBILITY: SOURCE
    PROJECT
    MAVEN
    CUSTOMER APP
    LIBRARY V2

    View full-size slide

  95. @MOLSJEROEN
    3. COMPATIBILITY: SOURCE
    class CustomerApp
    {

    fun useLibrary() = Library().initialize(
    )

    }

    // Library v1
    class Library {
    fun initialize() = Database().initialize()
    }

    View full-size slide

  96. @MOLSJEROEN
    3. COMPATIBILITY: SOURCE
    class CustomerApp
    {

    fun useLibrary() = Library().initialize(
    )

    }
    // Library v2
    class Library {
    fun initialize(useCaching: Boolean) = Database().initialize()
    }

    View full-size slide

  97. @MOLSJEROEN
    3. COMPATIBILITY: SOURCE
    class CustomerApp {
    fun useLibrary() = Library().initialize()
    }
    // Library v2
    class Library {
    fun initialize(useCaching: Boolean) = Database().initialize()
    }
    no value passed for parameter useCaching

    View full-size slide

  98. @MOLSJEROEN
    3. COMPATIBILITY: SOURCE
    class CustomerApp {
    fun useLibrary() = Library().initialize()
    }
    // Library v2
    class Library {
    fun initialize(useCaching: Boolean = true) = Database().initialize()
    }

    View full-size slide

  99. @MOLSJEROEN
    3. COMPATIBILITY: BINARY
    // Bytecode library v1
    public
    fi
    nal class com/jeroenmols/library/Library {
    public
    fi
    nal static initialize()
    V

    }
    // Bytecode library v2
    public
    fi
    nal class com/jeroenmols/library/Library {
    public
    fi
    nal static initialize(Z)
    V

    public static synthetic initialize$default(ZILjava/lang/Object;)
    V

    }

    View full-size slide

  100. @MOLSJEROEN
    3. COMPATIBILITY: BINARY
    // Bytecode library v
    1

    public
    fi
    nal class com/jeroenmols/library/Library {
    public
    fi
    nal static initialize()
    V

    }

    // Bytecode library v
    2

    public
    fi
    nal class com/jeroenmols/library/Library
    {

    public
    fi
    nal static initialize(Z)
    V

    public static synthetic initialize$default(ZILjava/lang/Object;)
    V

    }

    View full-size slide

  101. @MOLSJEROEN
    3. COMPATIBILITY: BINARY
    // Bytecode library v
    1

    public
    fi
    nal class com/jeroenmols/library/Library
    {

    public
    fi
    nal static initialize()
    V

    }

    // Bytecode library v
    2

    public
    fi
    nal class com/jeroenmols/library/Library {
    public
    fi
    nal static initialize(Z)
    V

    public static synthetic initialize$default(ZILjava/lang/Object;)
    V

    }

    View full-size slide

  102. @MOLSJEROEN
    3. COMPATIBILITY: BINARY
    OTHER LIBRARY
    MAVEN
    CUSTOMER APP
    LIBRARY V1
    LIBRARY V2

    View full-size slide

  103. @MOLSJEROEN
    3. COMPATIBILITY: BINARY
    OTHER LIBRARY
    MAVEN
    APP
    < FORCED >

    LIBRARY V2
    LIBRARY V2

    View full-size slide

  104. @MOLSJEROEN
    3. COMPATIBILITY: BINARY
    Maintaining Compatibility in Kotlin libraries by Márton Braun

    https://zsmb.co/maintaining-compatibility-in-kotlin-libraries/


    Public API challenges in Kotlin by Jake Wharton


    https://jakewharton.com/public-api-challenges-in-kotlin/

    View full-size slide

  105. @MOLSJEROEN
    3. COMPATIBILITY: GRADLE PLUGIN
    // top level build.gradle
    plugins {
    id 'org.jetbrains.kotlinx.binary-compatibility-validator' version '0.8.0-RC'
    }
    apiValidation {
    ignoredPackages += ["com.jeroenmols.library.internal",
    “com.jeroenmols.library.databinding"]
    ignoredClasses += ["com.jeroenmols.library.BuildCon
    fi
    g"]
    ignoredProjects += ["app"]
    }

    View full-size slide

  106. @MOLSJEROEN
    3. COMPATIBILITY: GRADLE PLUGIN
    // Snapshot current API into api subfolder
    $ ./gradlew apiDum
    p

    // Verify current API against snapshot
    $ ./gradlew apiCheck

    View full-size slide

  107. @MOLSJEROEN
    4. MINIMIZING API SURFACE
    • Easy to learn


    • Hard to make mistakes


    • Reduce maintenance


    • Avoid deprecation


    • Easier to change

    View full-size slide

  108. @MOLSJEROEN
    4. MINIMIZE API SURFACE
    // build.gradle
    fi
    le of each module
    android {
    ...
    kotlinOptions {
    freeCompilerArgs += '-Xexplicit-api=strict'
    }
    }
    // library
    class Library {
    fun initialize() { ... }
    }
    Visibility must be specified in explicit API mode

    View full-size slide

  109. @MOLSJEROEN
    4. MINIMIZE API SURFACE
    // build.gradle
    fi
    le of each module
    android {
    ...
    kotlinOptions {
    freeCompilerArgs += '-Xexplicit-api=strict'
    }
    }
    // librar
    y

    class Library
    {

    fun initialize() { ...
    }

    }

    View full-size slide

  110. @MOLSJEROEN
    4. MINIMIZE API SURFACE
    // build.gradle
    fi
    le of each modul
    e

    android
    {

    ..
    .

    kotlinOptions
    {

    freeCompilerArgs += '-Xexplicit-api=strict
    '

    }

    }
    // library
    class Library {
    fun initialize() { ... }
    }
    Visibility must be specified in explicit API mode

    View full-size slide

  111. @MOLSJEROEN
    5. EXPERIMENTAL APIS
    @RequiresOptIn(level = RequiresOptIn.Level.ERROR,
    message = "This is an experimental API.")
    @Retention(AnnotationRetention.BINARY)
    @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
    public annotation class ExperimentalMyLibraryApi

    View full-size slide

  112. @MOLSJEROEN
    5. EXPERIMENTAL APIS
    @RequiresOptIn(level = RequiresOptIn.Level.ERROR,
    message = "This is an experimental API.")
    @Retention(AnnotationRetention.BINARY
    )

    @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION
    )

    public annotation class ExperimentalMyLibraryApi

    View full-size slide

  113. @MOLSJEROEN
    5. EXPERIMENTAL APIS
    // library
    @ExperimentalMyLibraryApi
    fun doSomething()
    // ap
    p

    @OptIn(ExperimentalMyLibraryApi::class
    )

    override fun myFunction()
    {

    doSomething(
    )

    }

    View full-size slide

  114. @MOLSJEROEN
    5. EXPERIMENTAL APIS
    // librar
    y

    @ExperimentalMyLibraryAp
    i

    fun doSomething(
    )

    // app
    @OptIn(ExperimentalMyLibraryApi::class)
    override fun myFunction()
    {

    doSomething(
    )

    }

    View full-size slide

  115. @MOLSJEROEN
    5. EXPERIMENTAL APIS
    @kotlin.RequiresOptIn(
    level = kotlin.RequiresOptIn.Level.ERROR,
    )
    public annotation class ExperimentalMyLibraryAp
    i

    @androidx.annotation.RequiresOptIn(
    level = androidx.annotation.RequiresOptIn.Level.ERROR,
    )
    public annotation class ExperimentalMyLibraryApi

    View full-size slide

  116. @MOLSJEROEN
    6. DEPRECATION - REPLACE
    class Library
    {

    @Deprecated(message = “Please use the version with caching options”,
    replaceWith = ReplaceWith(“initialize(true)"))
    fun initialize(
    )

    fun initialize(useCaching: Boolean
    )

    }

    View full-size slide

  117. @MOLSJEROEN
    6. DEPRECATION - HIDE
    class Library
    {

    @Deprecated(message = “Please use the version with caching options”,
    level = DeprecationLevel.HIDDEN)
    fun initialize(
    )

    fun initialize(useCaching: Boolean
    )

    }

    View full-size slide

  118. @MOLSJEROEN
    7. PROGUARD
    android {
    defaultCon
    fi
    g {
    ...
    consumerProguardFiles 'consumer-rules.pro'
    }

    }

    View full-size slide

  119. @MOLSJEROEN
    DRIVE INNOVATION


    TOOLS ARE LACKING


    MINIMIZE API SURFACE


    LIMIT MODULARISATION


    REDUCE DEPENDENCIES

    View full-size slide

  120. @MOLSJEROEN
    HTTPS://JEROENMOLS.COM/BLOG

    View full-size slide

  121. @MOLSJEROEN
    IMAGE CREDITS
    Welcome image by James A. Molnar


    https://unsplash.com/photos/R8t7MjLXy2s


    Font awesome


    https://fontawesome.com/

    View full-size slide

  122. @MOLSJEROEN
    FURTHER READS
    Maintaining compatibility in Kotlin libraries by Márton Braun


    The hidden Kotlin gem: Deprecations with ReplaceWith by Marc Reichelt


    Publishing libraries to Maven Central in 2021 by Marton Braun


    Java interoperability policy for major version updates by Jake Wharton


    Public API challenges in Kotlin by Jake Wharton


    How to publish and distribute your Android library by Marco Gomiero


    What is a diamond dependency con
    fl
    ict by Google


    All about Opt-in annotations by Márton Braun

    View full-size slide