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

[Devoxx Belgium '18] Things I wish I knew when I started building Android Libraries/SDK -Vol.2

[Devoxx Belgium '18] Things I wish I knew when I started building Android Libraries/SDK -Vol.2

Building an Android library in the current times is way different than what it used to be earlier. Things have changed considerably and keeping up to date with them is a now a necessity than just some acquirable knowledge.

This session in a second part to the last year’s talk at Droidcon Berlin 2017 with the same title. In this session we will dive deeper into best practices and ways of architecting android libraries. You will get to learn about the common pitfalls and how to overcome them by using the right approach such as leveraging architecture components and making your android libraries lifecycle aware. We will also be covering how one can leverage Kotlin language when developing android libraries as well as information around API design and exploring the path to becoming a better Android Library Developer.

By the end of this session, you will be all set to build android libraries that scale and have API which contributes to developer happiness.

Google Slides: https://docs.google.com/presentation/d/1svOPXO9dYeuim1uHHymciIobdFKMb-4TKNUrvWmovH8/edit?usp=sharing

Video: https://www.youtube.com/watch?v=jQyt3HSmx2I

Ab4fa54bccd8073d0c0b4d4a2dd4193f?s=128

Nishant Srivastava

November 14, 2018
Tweet

Transcript

  1. None
  2. OSS Android libraries developed

  3. Android ARchive(AAR) @nisrulz

  4. AAR = Java ARchive(JAR) + Resources @nisrulz

  5. AAR = Java ARchive(JAR) + Resources @nisrulz Sensey: https://github.com/nisrulz/sensey

  6. AAR = Java ARchive(JAR) + Resources @nisrulz

  7. Using an AAR as dependency @nisrulz

  8. Using an AAR as dependency @nisrulz repositories{ flatDir{ dirs 'libs'

    } } dependencies { implementation(name:'nameOfYourAARFileWithoutExtension', ext:'aar') }
  9. Using an AAR as dependency @nisrulz repositories{ flatDir{ dirs 'libs'

    } } dependencies { implementation(name:'nameOfYourAARFileWithoutExtension', ext:'aar') } // None of the dependencies of the library are downloaded
  10. Why doesn’t my AAR download transitive dependencies? @nisrulz

  11. Sensey’s build.gradle @nisrulz dependencies { ... // Other testing dependencies

    // Transitive dependency: Support Compat library implementation "com.android.support:support-compat:27.0.2" }
  12. Sensey’s AAR @nisrulz

  13. Sensey’s AAR @nisrulz No build.gradle file

  14. Maven Artifact @nisrulz

  15. @nisrulz Maven Artifact

  16. POM? @nisrulz

  17. POM @nisrulz + Stands for Project Object Model + Is

    a XML file + Contains + Information about the project + Configuration details used by Maven to build the project
  18. POM @nisrulz POM of Sensey Android Library <?xml version="1.0" encoding="UTF-8"?>

    <project xsi:schemaLocation="..." xmlns="..."xmlns:xsi="..."> <modelVersion>4.0.0</modelVersion> <groupId>com.github.nisrulz</groupId><artifactId>sensey</artifactId><version>1.8.0</version> <packaging>aar</packaging><name>sensey</name> <description>Android library which makes playing with sensor events &amp; detecting gestures a breeze.</description> <url>https://github.com/nisrulz/sensey</url> <licenses> <license> <name>The Apache Software License, Version 2.0</name><url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> </license> </licenses> <developers> <developer><id>nisrulz</id><name>Nishant Srivastava</name><email>nisrulz@gmail.com</email></developer> </developers> <scm> <connection>https://github.com/nisrulz/sensey.git</connection> <developerConnection>https://github.com/nisrulz/sensey.git</developerConnection> <url>https://github.com/nisrulz/sensey</url> </scm> <dependencies> <dependency> <groupId>com.android.support</groupId> <artifactId>support-compat</artifactId><version>27.0.2</version> <scope>runtime</scope> </dependency> <dependency> ... </dependency> </dependencies> </project>
  19. POM @nisrulz POM of Sensey Android Library <?xml version="1.0" encoding="UTF-8"?>

    <project xsi:schemaLocation="..." xmlns="..."xmlns:xsi="..."> <modelVersion>4.0.0</modelVersion> <groupId>com.github.nisrulz</groupId><artifactId>sensey</artifactId><version>1.8.0</version> <packaging>aar</packaging><name>sensey</name> <description>Android library which makes playing with sensor events &amp; detecting gestures a breeze.</description> <url>https://github.com/nisrulz/sensey</url> <licenses> <license> <name>The Apache Software License, Version 2.0</name><url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> </license> </licenses> <developers> <developer><id>nisrulz</id><name>Nishant Srivastava</name><email>nisrulz@gmail.com</email></developer> </developers> <scm> <connection>https://github.com/nisrulz/sensey.git</connection> <developerConnection>https://github.com/nisrulz/sensey.git</developerConnection> <url>https://github.com/nisrulz/sensey</url> </scm> <dependencies> <dependency> <groupId>com.android.support</groupId> <artifactId>support-compat</artifactId><version>27.0.2</version> <scope>runtime</scope> </dependency> <dependency> ... </dependency> </dependencies> </project>
  20. POM @nisrulz POM of Sensey Android Library <?xml version="1.0" encoding="UTF-8"?>

    <project xsi:schemaLocation="..." xmlns="..."xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.android.support</groupId> <artifactId>support-compat</artifactId><version>27.0.2</version> <scope>runtime</scope> </dependency> <dependency> ... </dependency> </dependencies> </project>
  21. Bundled Proguard Configs @nisrulz

  22. Bundled Proguard Configs @nisrulz android { release { minifyEnabled true

    // Rules to be used during the AAR generation proguardFiles 'proguard-rules-for-building-library.pro' // Rules appended to the integrating app consumerProguardFiles 'proguard-rules-for-using-library.pro' } ... }
  23. Bundled Proguard Configs @nisrulz Fuel: https://github.com/kittinunf/Fuel

  24. Bundled Proguard Configs @nisrulz

  25. Bundled Proguard Configs @nisrulz # Fuel’s bundled Proguard file #

    Without specifically keeping this class, # callbacks on android don't function properly. -keep class com.github.kittinunf.fuel.android.util.AndroidEnvironment
  26. Bundled Proguard Configs @nisrulz # To check the merged configuration

    # Add the below to your current config -printconfiguration proguard-merged-config.txt
  27. Bundled Proguard Configs @nisrulz # DON'T DO THIS -dontobfuscate -optimizations

    !code/allocation/variable # Effectively no optimizations -keep public class * { public protected *; }
  28. Bundled Proguard Configs @nisrulz # DON'T DO THIS # Adding

    the below in library proguard rules disables # the optimizations in the Android app -dontoptimze
  29. Modularization @nisrulz

  30. Modularization @nisrulz

  31. Modularization @nisrulz EasyDeviceInfo: https://github.com/nisrulz/easydeviceinfo

  32. Modularization @nisrulz dependencies { def libVer = {latest_version} // Base

    + Ads Bundled Library implementation "com.github.nisrulz:easydeviceinfo:$libVer" // Base Library implementation "com.github.nisrulz:easydeviceinfo-base:$libVer" // Ads Library implementation "com.github.nisrulz:easydeviceinfo-ads:$libVer" }
  33. Modularization @nisrulz <!-- POM file for com.github.nisrulz:easydeviceinfo --> <project xsi:schemaLocation="..."

    xmlns="..." xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-ads</artifactId> <version>2.4.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.4.1</version> <scope>compile</scope> </dependency> ... </dependencies> </project>
  34. Modularization @nisrulz <!-- POM file for com.github.nisrulz:easydeviceinfo--> <project xsi:schemaLocation="..." xmlns="..."

    xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-ads</artifactId> <version>2.4.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.4.1</version> <scope>compile</scope> </dependency> ... </dependencies> </project>
  35. Modularization @nisrulz <!-- POM file for com.github.nisrulz:easydeviceinfo--> <project xsi:schemaLocation="..." xmlns="..."

    xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-ads</artifactId> <version>2.4.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.4.1</version> <scope>compile</scope> </dependency> ... </dependencies> </project>
  36. Modularization @nisrulz <!-- POM file for com.github.nisrulz:easydeviceinfo-ads --> <project xsi:schemaLocation="..."

    xmlns="..." xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-common</artifactId><version>2.4.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.android.gms</groupId> <artifactId>play-services-basement</artifactId><version>11.8.0</version> <scope>runtime</scope> </dependency> ... </dependencies> </project>
  37. Modularization @nisrulz <!-- POM file for com.github.nisrulz:easydeviceinfo-ads --> <project xsi:schemaLocation="..."

    xmlns="..." xmlns:xsi="..."> ... <dependencies> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-common</artifactId><version>2.4.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.android.gms</groupId> <artifactId>play-services-basement</artifactId><version>11.8.0</version> <scope>runtime</scope> </dependency> ... </dependencies> </project>
  38. Avoiding Resource Name Conflicts @nisrulz

  39. @nisrulz Conflict occurs between a library & app resource >

    Project will not compile What happens when..
  40. @nisrulz Conflict occurs between 2 libraries integrated in the app

    > Resources from library defined first in build.gradle gets included What happens when..
  41. @nisrulz Add a prefix to all your resources. Solution?

  42. @nisrulz Add a prefix to all your resources. Enforce this

    in Android Studio android { resourcePrefix 'YOUR_PREFIX_' // i.e 'sensey_' } Solution?
  43. @nisrulz Solution? Resource named ‘app_name’ does not start with the

    project’s resource prefix ‘sensey_’; Rename to `sensey_app_name`?
  44. minSdkVersion Restriction @nisrulz

  45. @nisrulz minSdkVersion of app >= minSdkVersion of library minSdk Restriction

  46. @nisrulz minSdk Restriction

  47. @nisrulz Manifest merger failed : uses-sdk:minSdkVersion 14 cannot be smaller

    than version 21 declared in library [:sensey] Suggestion: + Use a compatible library with a minSdk of at most 14 + Increase this project's minSdk version to at least 21 + Use tools:overrideLibrary="com.github.nisrulz.sensey" to force usage (may lead to runtime failures) minSdk Restriction
  48. @nisrulz Manifest merger failed : uses-sdk:minSdkVersion 14 cannot be smaller

    than version 21 declared in library [:sensey] Suggestion: + Use a compatible library with a minSdk of at most 14 + Increase this project's minSdk version to at least 21 + Use tools:overrideLibrary="com.github.nisrulz.sensey" to force usage (may lead to runtime failures) minSdk Restriction
  49. @nisrulz Manifest merger failed : uses-sdk:minSdkVersion 14 cannot be smaller

    than version 21 declared in library [:sensey] Suggestion: + Use a compatible library with a minSdk of at most 14 + Increase this project's minSdk version to at least 21 + Use tools:overrideLibrary="com.github.nisrulz.sensey" to force usage (may lead to runtime failures) minSdk Restriction
  50. @nisrulz <manifest xmlns:android="..." xmlns:tools="http://schemas.android.com/tools" package="com.github.nisrulz.senseysample"> <uses-sdk tools:overrideLibrary="com.github.nisrulz.sensey"/> <application ...> ...

    </application> </manifest> minSdk Restriction
  51. @nisrulz <manifest xmlns:android="..." xmlns:tools="http://schemas.android.com/tools" package="com.github.nisrulz.senseysample"> <uses-sdk tools:overrideLibrary="com.github.nisrulz.sensey"/> <application ...> ...

    </application> </manifest> minSdk Restriction
  52. Access Visibility vs Code Organization @nisrulz

  53. Visibility vs Organization @nisrulz + Code organized in individual packages;

    everything is public
  54. Visibility vs Organization @nisrulz + Code organized in individual packages;

    everything is public + Code organized inside one package; everything is package private and only public on demand
  55. Visibility vs Organization @nisrulz + Code organized in individual packages;

    everything is public + Code organized inside one package; everything is package private and only public on demand But if in Kotlin land, + Code organized in individual packages; everything is internal and only public on demand
  56. Visibility vs Organization @nisrulz // file name: exampleLibrary.kt // module

    name: example package com.example.library
  57. Visibility vs Organization @nisrulz // file name: exampleLibrary.kt // module

    name: example package com.example.library // visible inside exampleLibrary.kt private fun setup() { ... }
  58. Visibility vs Organization @nisrulz // file name: exampleLibrary.kt // module

    name: example package com.example.library // visible inside exampleLibrary.kt private fun setup() { ... } // property is visible everywhere public var name: String = "ExampleLib" // setter is visible only in exampleLibrary.kt private set{...}
  59. Visibility vs Organization @nisrulz // file name: exampleLibrary.kt // module

    name: example package com.example.library // visible inside exampleLibrary.kt private fun setup() { ... } // property is visible everywhere public var name: String = "ExampleLib" // setter is visible only in exampleLibrary.kt private set{...} // visible inside the module i.e example internal val debugTag = "Example-Debug"
  60. Lifecycle-Aware Android Library @nisrulz

  61. Lifecycle Components @nisrulz Classes designed to help deal with Android

    lifecycle + Lifecycle + LifecycleOwner + LifecycleObserver
  62. Lifecycle Components @nisrulz Classes designed to help deal with Android

    lifecycle
  63. Lifecycle Components @nisrulz Classes designed to help deal with Android

    lifecycle i.e Activity, Fragment, Service, Custom
  64. LifecycleObserver @nisrulz dependencies { def lifecycleVer = "1.1.1" // Runtime

    implementation "android.arch.lifecycle:runtime:$lifecycleVer" // Annotation Support annotationProcessor "android.arch.lifecycle:compiler:$lifecycleVer" ... }
  65. LifecycleObserver @nisrulz object AwesomeLibrary : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun init()

    { ... } @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStart() { ... } ... @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun cleanup() { ...} }
  66. LifecycleOwner @nisrulz class MainActivity : AppCompatActivity() { // LifecycleOwner override

    fun onResume() { // Add lifecycle observer lifecycle.addObserver(AwesomeLibrary.INSTANCE) } override fun onStop() { // Remove lifecycle observer lifecycle.removeObserver(AwesomeLibrary.INSTANCE) } }
  67. ProcessLifecycleOwner @nisrulz Class that tracks the lifecycle of whole application

    process
  68. ProcessLifecycleOwner @nisrulz dependencies { def lifecycleVer = "1.1.1" // For

    ProcessLifecycleOwner implementation "android.arch.lifecycle:extensions:$lifecycleVer" ... }
  69. ProcessLifecycleOwner @nisrulz extension artifact automatically adds <provider> element to your

    manifest
  70. ProcessLifecycleOwner @nisrulz <manifest > <application> ... <provider android:name="android.arch.lifecycle.ProcessLifecycleOwnerInitializer" android:authorities="com.example.app.lifecycle-trojan" android:exported="false"

    android:multiprocess="true" /> </application> </manifest>
  71. ProcessLifecycleOwner @nisrulz // Source code // Internal class to initialize

    Lifecycles. public class ProcessLifecycleOwnerInitializer extends ContentProvider { @Override public boolean onCreate() { ... ProcessLifecycleOwner.init(getContext()); return true; } ... }
  72. ProcessLifecycleOwner @nisrulz Initializes ProcessLifecycleOwner even if your app does not

    use it! Why? To invoke ProcessLifecycleOwner as soon as process starts
  73. Auto Initialize Android Library @nisrulz

  74. AutoInit Android Library @nisrulz Android Libraries need Android context to

    handle simple tasks such as + Hook into Android Runtime + Access app resources + Use System Services + Register BroadcastReceiver
  75. AutoInit Android Library @nisrulz public class MyApplication extends Application {

    @Override public void onCreate() { super.onCreate(); // Init android library MyAwesomeLibrary.init(this); } }
  76. AutoInit Android Library @nisrulz public class MyApplication extends Application {

    @Override public void onCreate() { super.onCreate(); // Init android library MyAwesomeLibrary.init(this); } }
  77. AutoInit Android Library @nisrulz public class MyApplication extends Application {

    @Override public void onCreate() { super.onCreate(); // Init android library MyAwesomeLibrary.init(this); } }
  78. AutoInit Android Library @nisrulz <application android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:name=".MyApplication" ... >

  79. AutoInit Android Library @nisrulz ContentProvider “can be” used to simplify

    the process. How? ContentProvider is + Created and initialized (on the main thread) before all other components + Participate in manifest merging at build time.
  80. AutoInit Android Library @nisrulz public class AwesomeLibInitProvider extends ContentProvider {

    ... @Override public boolean onCreate() { // get the context (Application context) Context context = getContext(); // initialize AwesomeLib here AwesomeLib.getInstance().init(context); return false; } ... }
  81. AutoInit Android Library @nisrulz public class AwesomeLibInitProvider extends ContentProvider {

    ... @Override public boolean onCreate() { // get the context (Application context) Context context = getContext(); // initialize AwesomeLib here AwesomeLib.getInstance().init(context); return false; } ... }
  82. AutoInit Android Library @nisrulz <manifest xmlns:android=".." package="github.nisrulz.sample.awesomelib"> <application> <provider android:name=".AwesomeLibInitProvider"

    android:authorities="${applicationId}.awesomelibinitprovider" android:enabled="true" android:exported="false"></provider> </application> </manifest>
  83. AutoInit Android Library @nisrulz <manifest xmlns:android=".." package="github.nisrulz.sample.awesomelib"> <application> <provider android:name=".AwesomeLibInitProvider"

    android:authorities="${applicationId}.awesomelibinitprovider" android:enabled="true" android:exported="false"></provider> </application> </manifest>
  84. AutoInit Android Library @nisrulz <manifest xmlns:android=".." package="github.nisrulz.sample.awesomelib"> <application> <provider android:name=".AwesomeLibInitProvider"

    android:authorities="${applicationId}.awesomelibinitprovider" android:enabled="true" android:exported="false"></provider> </application> </manifest>
  85. AutoInit Android Library @nisrulz <manifest xmlns:android=".." package="github.nisrulz.sample.awesomelib"> <application> <provider android:name=".AwesomeLibInitProvider"

    android:authorities="${applicationId}.awesomelibinitprovider" android:enabled="true" android:exported="false"></provider> </application> </manifest>
  86. AutoInit Android Library @nisrulz ContentProvider “can be” used to simplify

    the process. Why it shouldn’t be + There can be only one Content Provider with a given “authority” string + Only run on main thread; does not support multi process
  87. Links/References @nisrulz Android Libraries I have built: https://github.com/nisrulz/nisrulz.github.io#open-source-co ntributions Auto

    initialize android library example: https://github.com/nisrulz/android-examples/tree/develop/Au toInitLibrary Lifecycle Aware android library example: https://github.com/nisrulz/android-examples/tree/develop/Li feCycleCompForLib
  88. twitter.com/nisrulz github.com/nisrulz www.nisrulz.com

  89. twitter.com/nisrulz github.com/nisrulz www.nisrulz.com