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

Library Developer Confidential

Library Developer Confidential

For the last several years of my career I have been tasked with creating and maintaining libraries that handle complex bluetooth communication and Android UI. In this talk we will go over setting up library projects for success, bootstrapping an architecture that will allow you to grow with minimal collisions and boilerplate, how to build/deploy multiple libraries while trying to debug without losing your sanity and more. At the end of this talk it is my hope you will feel confident enough to start and maintain your own library project. This talk is part gradle, part java/kotlin and absolutely free of Git Submodules.

Elliott Chenger

July 19, 2018
Tweet

More Decks by Elliott Chenger

Other Decks in Programming

Transcript

  1. Elliott Chenger
    @echenger
    Library Developer Confidential

    View full-size slide

  2. AAR == Android Archive

    View full-size slide

  3. AAR == Android Archive
    Just a fancy zip

    View full-size slide

  4. Anatomy of an AAR
    classes.jar
    AndroidManifest.xml
    Res R.txt
    Public.txt

    View full-size slide

  5. Anatomy of an AAR
    assets
    JNI
    Lib JARs Proguard.txt
    lint.jar

    View full-size slide

  6. Why should you build a
    library?

    View full-size slide

  7. Faster builds
    Architecture and Portability
    Android App Bundles and Instant Apps

    View full-size slide

  8. class Pug {
    companion object {
    val isHungry = true
    }
    }

    View full-size slide

  9. class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    }
    }

    View full-size slide

  10. class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val textView = findViewById(R.id.text_view)
    textView.text = "Pugs are always hungry ${Pug.isHungry}"
    }
    }

    View full-size slide

  11. class Pug {
    companion object {
    val isHungry = true
    }
    }

    View full-size slide

  12. class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    }
    }

    View full-size slide

  13. :app 7s
    :pugs 7s

    View full-size slide

  14. –Library Skeptic
    “Nothing changed!”

    View full-size slide

  15. class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val textView = findViewById(R.id.text_view)
    textView.text = "Pugs are always hungry ${Pug.isHungry()}"
    }
    }

    View full-size slide

  16. :app 7s
    :pugs UP-TO-DATE

    View full-size slide

  17. :app 7s
    :pugs UP-TO-DATE

    View full-size slide

  18. Build Caching
    Added 3.5

    View full-size slide

  19. Build Caching
    Added 3.5
    Support for Build Caching
    Added 3.0.0

    View full-size slide

  20. - Gradle Documentation
    “The best way to do work faster is to not do work that doesn’t need
    doing.”

    View full-size slide

  21. Feature Based Architecture

    View full-size slide

  22. App
    Feat. A
    Feat. B

    View full-size slide

  23. Removing features is easier

    View full-size slide

  24. App
    Feat. A
    Feat. B

    View full-size slide

  25. dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation project(':pugs')
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }

    View full-size slide

  26. dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation project(':pugs')
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }

    View full-size slide

  27. dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation project(':pugs')
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }

    View full-size slide

  28. dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }

    View full-size slide

  29. Sharing features

    View full-size slide

  30. App 1
    Feat. A
    App 2

    View full-size slide

  31. Android App Bundles and Instant Apps

    View full-size slide

  32. App
    Feature A
    Instant
    App

    View full-size slide

  33. App
    Feature A
    Instant
    App

    View full-size slide

  34. App
    Feature A
    Instant
    App
    Util
    Lib

    View full-size slide

  35. Project Setup

    View full-size slide

  36. Single Project

    View full-size slide

  37. Only deploys in one app?
    Does it need to be shared with others?

    View full-size slide

  38. Only deploys in one app? Yes
    Does it need to be shared with others? No

    View full-size slide

  39. App Lib
    Single Project

    View full-size slide

  40. Settings.gradle
    include ':app', ':pugs'

    View full-size slide

  41. Build.gradle
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:support-annotations:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation project(":pugs")
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:
    3.0.2'
    }

    View full-size slide

  42. Build.gradle
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:support-annotations:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation project(":pugs")
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:
    3.0.2'
    }

    View full-size slide

  43. Multi-Project

    View full-size slide

  44. Only deploys in one app?
    Does it need to be shared with others?

    View full-size slide

  45. Only deploys in one app? No
    Does it need to be shared with others? Yes

    View full-size slide

  46. App Lib
    Multi-Project

    View full-size slide

  47. App
    Lib
    Multi-Project
    Test App

    View full-size slide

  48. App
    Lib
    Multi-Project
    Test App

    View full-size slide

  49. AAR in a Libs Directory
    Library.aar

    View full-size slide

  50. dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation files('libs/pugs-debug.aar')
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }

    View full-size slide

  51. dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation files('libs/pugs-debug.aar')
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }

    View full-size slide

  52. AAR in a Libs Directory
    repositories{
    flatDir {
    dirs '../libs'
    }
    mavenCentral()
    }

    View full-size slide

  53. AAR in a Libs Directory
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:support-annotations:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //Including a local gradle module
    implementation project(":pugs")
    //Including a local aar
    implementation(name:'beers-debug', ext:'aar')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    }

    View full-size slide

  54. AAR in a Libs Directory
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:support-annotations:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //Including a local gradle module
    implementation project(":pugs")
    //Including a local aar
    implementation(name:'beers-debug', ext:'aar')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    }

    View full-size slide

  55. 07-01 17:50:12.887 4565-4565/com.omitneedlesscode.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.omitneedlesscode.myapplication, PID: 4565
    java.lang.NoClassDefFoundError: Failed resolution of: Ltimber/log/Timber$DebugTree;
    at com.omitneedlesscode.beers.BeerApplication.onCreate(BeerApplication.kt:10)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1118)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5791)
    at android.app.ActivityThread.-wrap1(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1661)
    at android.os.Handler.dispatchMessage(Handler.java:105)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6541)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

    View full-size slide

  56. 07-01 17:50:12.887 4565-4565/com.omitneedlesscode.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.omitneedlesscode.myapplication, PID: 4565
    java.lang.NoClassDefFoundError: Failed resolution of: Ltimber/log/Timber$DebugTree;
    at com.omitneedlesscode.beers.BeerApplication.onCreate(BeerApplication.kt:10)
    at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1118)
    at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5791)
    at android.app.ActivityThread.-wrap1(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1661)
    at android.os.Handler.dispatchMessage(Handler.java:105)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6541)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

    View full-size slide

  57. public final fun getBreweries():kotlin.collections.List { /* compiled code */ }
    Note: AARs don’t contain source

    View full-size slide

  58. AAR in a Libs Directory
    Library.aar

    View full-size slide

  59. AAR in a Libs Directory
    Library.aar
    +
    Copy of Dependencies

    View full-size slide

  60. AAR in a Libs Directory
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:support-annotations:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //Including a local gradle module
    implementation project(":pugs")
    //Including a local aar
    implementation(name:'beers-debug', ext:’aar’)
    implementation 'com.jakewharton.timber:timber:4.7.1'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    }

    View full-size slide

  61. AAR in a Libs Directory
    dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:support-annotations:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //Including a local gradle module
    implementation project(":pugs")
    //Including a local aar
    implementation(name:'beers-debug', ext:’aar’)
    implementation 'com.jakewharton.timber:timber:4.7.1'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    }

    View full-size slide

  62. AAR in a Libs Directory
    •Easy to use
    •Quick to test changes
    •Copy over library
    dependencies
    •Project switching
    •GIT LFS or your repo
    size will balloon
    •Hard to debug
    Pros Cons

    View full-size slide

  63. App
    Lib
    Gradle Composite Project
    Test App

    View full-size slide

  64. App
    Lib
    Gradle Composite Project
    Test App
    Lib
    Test App

    View full-size slide

  65. Settings.gradle
    include ':app'
    includeBuild('../Beers'){
    dependencySubstitution{
    substitute module('com.omitneedlesscode:beers') with project(':beers')
    }
    }

    View full-size slide

  66. Note
    // Gradle Composite Builds won't work with local AARs
    implementation(name:'beers-debug', ext:'aar')
    // They do work with Maven Dependencies
    implementation 'com.omitneedlesscode:beers:1.0'

    View full-size slide

  67. Note
    // Gradle Composite Builds won't work with local AARs
    implementation(name:'beers-debug', ext:'aar')
    // They do work with Maven Dependencies
    implementation 'com.omitneedlesscode:beers:1.0'

    View full-size slide

  68. •Doesn’t impact other
    developers
    •Can change code in both
    library and app
    •No major modifications to
    the library project gradle
    files
    •Easier to debug
    •Newer feature so there
    have been some small
    bugs
    •To turn off composite
    mode may need to re-
    import library project
    •Can’t use with provided
    aars in Lib
    Pros Cons
    Gradle Composite Project

    View full-size slide

  69. What is a Maven Dependency?

    View full-size slide

  70. –Maven Documentation
    “A repository in Maven is used to hold build artifacts and
    dependencies of varying types.”

    View full-size slide

  71. –Maven Documentation
    “A repository in Maven is used to hold build artifacts and
    dependencies of varying types.”

    View full-size slide

  72. Library.aar
    +
    pom.xml

    View full-size slide

  73. Maven Repo
    Library.aar
    +
    pom.xml

    View full-size slide

  74. Maven Dependency Naming Convention
    ::
    'com.omitneedlesscode:beers:1.0'

    View full-size slide

  75. How do you create a Maven
    Dependency?

    View full-size slide

  76. apply plugin: 'maven-publish'

    View full-size slide

  77. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'beers'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/beers-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  78. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'beers'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/beers-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  79. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'beers'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/beers-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  80. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'beers'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/beers-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  81. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'beers'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/beers-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  82. How do I test my maven
    dependency?

    View full-size slide

  83. Maven Repo
    Library.aar
    +
    pom.xml

    View full-size slide

  84. Maven Repo
    Library.aar
    +
    pom.xml
    Local

    View full-size slide

  85. 11:06:46 PM: Executing task 'publishToMavenLocal'...
    Executing tasks: [publishToMavenLocal]
    Configuration on demand is an incubating feature.
    :beers:generatePomFileForAarPublication
    :beers:publishAarPublicationToMavenLocal
    :beers:publishToMavenLocal
    BUILD SUCCESSFUL in 5s
    2 actionable tasks: 2 executed
    11:06:51 PM: Task execution finished 'publishToMavenLocal'.

    View full-size slide

  86. ➜ ~ cd ~/.m2/

    View full-size slide

  87. /Users//.m2/repository/com/omitneedlesscode/beers/1.0

    View full-size slide

  88. ➜ 1.0 ls
    beers-1.0.aar beers-1.0.pom

    View full-size slide

  89. repositories{
    flatDir {
    dirs '../libs'
    }
    mavenCentral()
    mavenLocal()
    }

    View full-size slide

  90. repositories{
    flatDir {
    dirs '../libs'
    }
    mavenCentral()
    mavenLocal()
    }

    View full-size slide

  91. dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:support-annotations:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //Including a local gradle module
    implementation project(":pugs")
    //Including a local aar
    implementation ‘com.omitneedlesscode:beers:1.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    }

    View full-size slide

  92. dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support:support-annotations:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    //Including a local gradle module
    implementation project(":pugs")
    //Including a local aar
    implementation 'com.omitneedlesscode:beers:1.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    }

    View full-size slide

  93. App Lib 1
    build.gradle build.gradle

    View full-size slide

  94. App Lib 1
    build.gradle build.gradle
    Lib 2
    build.gradle

    View full-size slide

  95. App Lib 1
    build.gradle build.gradle
    Lib 2
    build.gradle
    … Lib N
    build.gradle

    View full-size slide

  96. Shared Gradle Scripts

    View full-size slide

  97. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'pugs'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/pugs-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  98. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'pugs'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/pugs-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  99. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'beers'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/beers-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  100. publishing{
    publications{
    aar(MavenPublication){
    groupId 'com.omitneedlesscode'
    artifactId 'beers'
    version = android.defaultConfig.versionName
    artifact new File('build/outputs/aar/beers-debug.aar')
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each{
    if(it.name!="unspecified"){
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  101. artifact new File('build/outputs/aar/pugs-debug.aar')
    artifact new File('build/outputs/aar/beers-debug.aar')
    artifactId 'beers'
    artifactId 'pugs'

    View full-size slide

  102. publish.gradle

    View full-size slide

  103. apply plugin: 'maven-publish'
    ext.filePath = ''
    ext.expectedArtifactId = ''
    ext.expectedGroupId = ''
    ext.expectedVersion = ''
    publishing {
    publications {
    aar(MavenPublication) {
    groupId expectedGroupId
    artifactId expectedArtifactId
    version = expectedVersion
    artifact new File(filePath)
    pom.withXml {
    def dependencies = asNode().appendNode('dependencies')
    configurations.implementation.allDependencies.each {
    if (it.name != "unspecified") {
    def dependency = dependencies.appendNode('dependency')
    dependency.appendNode('groupId', it.group)
    dependency.appendNode('artifactId', it.name)
    dependency.appendNode('version', it.version)
    }
    }
    }
    }
    }
    }

    View full-size slide

  104. ext.filePath = ''
    ext.expectedArtifactId = ''
    ext.expectedGroupId = ''
    ext.expectedVersion = ''

    View full-size slide

  105. apply from: '../publish.gradle'
    ext.filePath = 'build/outputs/aar/cars-debug.aar'
    ext.expectedArtifactId = 'cars'
    ext.expectedGroupId = 'com.omitneedlesscode'
    ext.expectedVersion = app_version

    View full-size slide

  106. Remote Shared Gradle Scripts

    View full-size slide

  107. apply from: “https://foo.bar/publish.gradle”
    ext.filePath = 'build/outputs/aar/cars-debug.aar'
    ext.expectedArtifactId = 'cars'
    ext.expectedGroupId = 'com.omitneedlesscode'
    ext.expectedVersion = app_version

    View full-size slide

  108. apply from: “https://foo.bar/publish.gradle”
    ext.filePath = 'build/outputs/aar/cars-debug.aar'
    ext.expectedArtifactId = 'cars'
    ext.expectedGroupId = 'com.omitneedlesscode'
    ext.expectedVersion = app_version

    View full-size slide

  109. App Lib 1
    build.gradle build.gradle
    Lib 2
    build.gradle
    … Lib N
    build.gradle
    publish.gradle

    View full-size slide

  110. ./gradlew --profile --recompile-scripts --offline assembleDebug

    View full-size slide

  111. Avoid Creating Dependency
    Hell

    View full-size slide

  112. App
    Lib
    glide:3.8.0
    glide:4.7.1

    View full-size slide

  113. 21:28:57.809 13647-13647/com.omitneedlesscode.nodependency E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.omitneedlesscode.nodependency, PID: 13647
    java.lang.NoSuchMethodError: No virtual method load(Ljava/lang/String;)Lcom/bumptech/glide/
    DrawableTypeRequest; in class Lcom/bumptech/glide/RequestManager; or its super classes (declaration of
    'com.bumptech.glide.RequestManager' appears in /data/app/com.omitneedlesscode.nodependency-m1gfBVcawag-
    PWVRV_wyZQ==/split_lib_dependencies_apk.apk)
    at com.omitneedlesscode.pugs.PugActivity.onCreate(PugActivity.kt:16)
    at android.app.Activity.performCreate(Activity.java:6975)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
    at android.app.ActivityThread.-wrap11(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
    at android.os.Handler.dispatchMessage(Handler.java:105)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6541)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

    View full-size slide

  114. 21:28:57.809 13647-13647/com.omitneedlesscode.nodependency E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.omitneedlesscode.nodependency, PID: 13647
    java.lang.NoSuchMethodError: No virtual method load(Ljava/lang/String;)Lcom/bumptech/glide/
    DrawableTypeRequest; in class Lcom/bumptech/glide/RequestManager; or its super classes (declaration of
    'com.bumptech.glide.RequestManager' appears in /data/app/com.omitneedlesscode.nodependency-m1gfBVcawag-
    PWVRV_wyZQ==/split_lib_dependencies_apk.apk)
    at com.omitneedlesscode.pugs.PugActivity.onCreate(PugActivity.kt:16)
    at android.app.Activity.performCreate(Activity.java:6975)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
    at android.app.ActivityThread.-wrap11(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
    at android.os.Handler.dispatchMessage(Handler.java:105)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6541)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)

    View full-size slide

  115. App
    Lib
    glide:3.8.0
    glide:4.7.1

    View full-size slide

  116. Do you really need that
    library?

    View full-size slide

  117. But do you need it in your
    library?

    View full-size slide

  118. Not necessarily

    View full-size slide

  119. Command Pattern

    View full-size slide

  120. Command Pattern
    Deferred Responsibility Pattern

    View full-size slide

  121. Library
    interface AppUtilities {
    var imageFetcher: ImageFetcher
    }

    View full-size slide

  122. Library
    interface ImageFetcher {
    fun loadImageInView(imageUrl: String, defaultImageResource: Int, view: ImageView)
    }

    View full-size slide

  123. App
    class GlideImageFetcher: ImageFetcher {
    override fun loadImageInView(imageUrl: String, defaultImageResource: Int, view: ImageView) {
    GlideApp.with(view).load(imageUrl).placeholder(defaultImageResource).into(view)
    }
    }

    View full-size slide

  124. App
    Lib
    glide:3.8.0
    glide:4.7.1

    View full-size slide

  125. Content Providers

    View full-size slide

  126. android:grantUriPermissions="true"
    android:exported="false"
    android:authorities=“com.omitneedlesscode.fooprovider”
    android:name=".FooFileProvider">
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/filepaths"
    />

    View full-size slide

  127. android:grantUriPermissions="true"
    android:exported="false"
    android:authorities=“com.omitneedlesscode.fooprovider”
    android:name=".FooFileProvider">
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/filepaths"
    />

    View full-size slide

  128. Providers have one authority

    View full-size slide

  129. Android Manifest Placeholders

    View full-size slide

  130. android {
    defaultConfig {
    manifestPlaceholders = [hostName:"www.example.com"]
    }
    ...
    }

    View full-size slide

  131. android:grantUriPermissions="true"
    android:exported="false"
    android:authorities="${applicationId}.fooprovider"
    android:name=".FooFileProvider">
    android:name="android.support.FILE_PROVIDER_PATHS"
    android:resource="@xml/filepaths"
    />

    View full-size slide

  132. Overriding IDs
    Lib
    App
    Artoo
    R2

    View full-size slide

  133. Overriding IDs
    Lib
    App
    Artoo
    R2

    View full-size slide

  134. Overriding IDs
    build variant
    build type
    product flavor
    main source set
    library dependencies

    View full-size slide

  135. Overriding IDs
    build variant
    build type
    product flavor
    main source set
    library dependencies

    View full-size slide

  136. –Someone
    “Why would I want to override resource IDs?”

    View full-size slide

  137. App Specific Content

    View full-size slide

  138. Overriding IDs
    Lib
    App
    Map My Ride
    Map My Run

    View full-size slide

  139. Overriding IDs
    Strings
    Drawables Values
    Animations

    View full-size slide

  140. Thanks!
    @echenger

    View full-size slide