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.

9904145db476b6658d75fff27b1929ed?s=128

Elliott Chenger

July 19, 2018
Tweet

Transcript

  1. Elliott Chenger @echenger Library Developer Confidential

  2. None
  3. Wall-E Artoo

  4. AAR == Android Archive

  5. AAR == Android Archive Just a fancy zip

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

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

  8. Why should you build a library?

  9. Faster builds Architecture and Portability Android App Bundles and Instant

    Apps
  10. Speed

  11. App

  12. class Pug { companion object { val isHungry = true

    } }
  13. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
  14. :app 14s

  15. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView = findViewById<TextView>(R.id.text_view) textView.text = "Pugs are always hungry ${Pug.isHungry}" } }
  16. :app 14s

  17. App

  18. App Lib

  19. class Pug { companion object { val isHungry = true

    } }
  20. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
  21. :app 7s :pugs 7s

  22. –Library Skeptic “Nothing changed!”

  23. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val textView = findViewById<TextView>(R.id.text_view) textView.text = "Pugs are always hungry ${Pug.isHungry()}" } }
  24. :app 7s :pugs UP-TO-DATE

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

  26. Build Caching Added 3.5

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

  28. - Gradle Documentation “The best way to do work faster

    is to not do work that doesn’t need doing.”
  29. None
  30. Feature Based Architecture

  31. App

  32. App Feat. A Feat. B

  33. Removing features is easier

  34. App Feat. A Feat. B

  35. App Feat. A

  36. 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' }
  37. 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' }
  38. 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' }
  39. 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' }
  40. Sharing features

  41. App 1 Feat. A App 2

  42. Android App Bundles and Instant Apps

  43. App

  44. App Feature A Instant App

  45. App Feature A Instant App

  46. App Feature A Instant App Util Lib

  47. Project Setup

  48. Single Project

  49. Only deploys in one app? Does it need to be

    shared with others?
  50. Only deploys in one app? Yes Does it need to

    be shared with others? No
  51. App Lib Single Project

  52. None
  53. None
  54. None
  55. None
  56. Settings.gradle include ':app', ':pugs'

  57. 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' }
  58. 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' }
  59. Multi-Project

  60. Only deploys in one app? Does it need to be

    shared with others?
  61. Only deploys in one app? No Does it need to

    be shared with others? Yes
  62. App Lib Multi-Project

  63. App Lib Multi-Project Test App

  64. App Lib Multi-Project Test App

  65. AAR in a Libs Directory Library.aar

  66. 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' }
  67. 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' }
  68. OR

  69. AAR in a Libs Directory repositories{ flatDir { dirs '../libs'

    } mavenCentral() }
  70. 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" }
  71. 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" }
  72. 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)
  73. 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)
  74. public final fun getBreweries():kotlin.collections.List<kotlin.String> { /* compiled code */ }

    Note: AARs don’t contain source
  75. AAR in a Libs Directory Library.aar

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

  77. 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" }
  78. 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" }
  79. 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
  80. App Lib Gradle Composite Project Test App

  81. App Lib Gradle Composite Project Test App Lib Test App

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

    }
  83. 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'
  84. 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'
  85. None
  86. None
  87. None
  88. •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
  89. What is a Maven Dependency?

  90. –Maven Documentation “A repository in Maven is used to hold

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

    build artifacts and dependencies of varying types.”
  92. Library.aar + pom.xml

  93. Maven Repo Library.aar + pom.xml

  94. Maven Dependency Naming Convention <group id>:<artifact id>:<version> 'com.omitneedlesscode:beers:1.0'

  95. How do you create a Maven Dependency?

  96. apply plugin: 'maven-publish'

  97. 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) } } } } } }
  98. 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) } } } } } }
  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) } } } } } }
  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) } } } } } }
  101. 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) } } } } } }
  102. How do I test my maven dependency?

  103. Maven Repo Library.aar + pom.xml

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

  105. None
  106. 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'.
  107. ➜ ~ cd ~/.m2/

  108. /Users/<username>/.m2/repository/com/omitneedlesscode/beers/1.0

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

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

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

  112. 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" }
  113. 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" }
  114. Scripting

  115. App Lib 1 build.gradle build.gradle

  116. App Lib 1 build.gradle build.gradle Lib 2 build.gradle

  117. App Lib 1 build.gradle build.gradle Lib 2 build.gradle … Lib

    N build.gradle
  118. Shared Gradle Scripts

  119. 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) } } } } } }
  120. 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) } } } } } }
  121. 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) } } } } } }
  122. 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) } } } } } }
  123. artifact new File('build/outputs/aar/pugs-debug.aar') artifact new File('build/outputs/aar/beers-debug.aar') artifactId 'beers' artifactId 'pugs'

  124. publish.gradle

  125. 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) } } } } } }
  126. ext.filePath = '' ext.expectedArtifactId = '' ext.expectedGroupId = '' ext.expectedVersion

    = ''
  127. apply from: '../publish.gradle' ext.filePath = 'build/outputs/aar/cars-debug.aar' ext.expectedArtifactId = 'cars' ext.expectedGroupId

    = 'com.omitneedlesscode' ext.expectedVersion = app_version
  128. Remote Shared Gradle Scripts

  129. 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
  130. 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
  131. App Lib 1 build.gradle build.gradle Lib 2 build.gradle … Lib

    N build.gradle publish.gradle
  132. ./gradlew --profile --recompile-scripts --offline assembleDebug

  133. Avoid Creating Dependency Hell

  134. App Lib glide:3.8.0 glide:4.7.1

  135. 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)
  136. 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)
  137. App Lib glide:3.8.0 glide:4.7.1

  138. Do you really need that library?

  139. Yes

  140. But do you need it in your library?

  141. Not necessarily

  142. Command Pattern

  143. Command Pattern Deferred Responsibility Pattern

  144. Library interface AppUtilities { var imageFetcher: ImageFetcher }

  145. Library interface ImageFetcher { fun loadImageInView(imageUrl: String, defaultImageResource: Int, view:

    ImageView) }
  146. App class GlideImageFetcher: ImageFetcher { override fun loadImageInView(imageUrl: String, defaultImageResource:

    Int, view: ImageView) { GlideApp.with(view).load(imageUrl).placeholder(defaultImageResource).into(view) } }
  147. App Lib glide:3.8.0 glide:4.7.1

  148. Manifests

  149. Content Providers

  150. <provider android:grantUriPermissions="true" android:exported="false" android:authorities=“com.omitneedlesscode.fooprovider” android:name=".FooFileProvider"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider>

  151. <provider android:grantUriPermissions="true" android:exported="false" android:authorities=“com.omitneedlesscode.fooprovider” android:name=".FooFileProvider"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider>

  152. Providers have one authority

  153. Android Manifest Placeholders

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

  155. <provider android:grantUriPermissions="true" android:exported="false" android:authorities="${applicationId}.fooprovider" android:name=".FooFileProvider"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" /> </provider>

  156. Resources

  157. Overriding IDs Lib App <string name="first_pug_name">Artoo</string> <string name="first_pug_name">R2</string>

  158. Overriding IDs Lib App <string name="first_pug_name">Artoo</string> <string name="first_pug_name">R2</string>

  159. Overriding IDs build variant build type product flavor main source

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

    set library dependencies
  161. –Someone “Why would I want to override resource IDs?”

  162. App Specific Content

  163. Overriding IDs Lib App <string name=“app_name”>Map My Ride</string> <string name=“app_name”>Map

    My Run</string>
  164. Overriding IDs Strings Drawables Values Animations

  165. Thanks! @echenger