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

[Mobius '18] Things I wish I knew when I starte...

[Mobius '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 now a necessity rather than just some acquirable knowledge.

This session is a second part to the last year's talk at Droidcon Berlin 2017 which was Vol. 1. In this session, you 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. You will also understand 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 the developer's happiness.

Nishant Srivastava

December 08, 2018
Tweet

More Decks by Nishant Srivastava

Other Decks in Technology

Transcript

  1. 1

  2. Side A 1. How does Gradle process Android Libraries 2.

    Android Archive(AAR) 3. Why doesn’t my AAR download transitive dependencies? 4. Maven Artifact 5. POM? 6. Bundled Proguard Configs Side B 7. Modularization 8. Avoiding Resource Name Conflicts 9. minSdkVersion Restriction 10. Access Visibility vs Code Organization 11. Lifecycle Aware Android Library 12. Auto Init Android Library 3 @nisrulz
  3. How does Gradle process Android Libraries repositories{ jCenter() } dependencies

    { implementation 'com.example.myawesomelib:1.0.0' ... } 5 @nisrulz
  4. Sensey’s build.gradle dependencies { ... // Other testing dependencies //

    Transitive dependency: Support Compat library implementation "com.android.support:support-compat:27.0.2" } 13 @nisrulz
  5. Using an AAR as dependency repositories{ flatDir{ dirs 'libs' }

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

    } dependencies { implementation(name:'nameOfYourAARFileWithoutExtension', ext:'aar') } // No transitive dependencies of the library are downloaded 16 @nisrulz
  7. POM • Project Object Model • XML file • Configuration

    details used by Maven to build the project 23 @nisrulz
  8. POM 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>[email protected]</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> 24 @nisrulz
  9. POM 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>[email protected]</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> 25 @nisrulz
  10. POM 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> 26 @nisrulz
  11. Bundled Proguard Configs 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' } ... } 28 @nisrulz
  12. Bundled Proguard Configs # 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 31 @nisrulz
  13. Bundled Proguard Configs # To check the merged configuration #

    Add the below to your current config -printconfiguration proguard-merged-config.txt 32 @nisrulz
  14. Bundled Proguard Configs # DON'T DO THIS -dontobfuscate -optimizations !code/allocation/variable

    # Effectively no optimizations -keep public class * { public protected *; } 33 @nisrulz
  15. Bundled Proguard Configs # DON'T DO THIS # Adding the

    below in library proguard rules disables # the optimizations in the Android app -dontoptimize 34 @nisrulz
  16. Modularization 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" } 38 @nisrulz
  17. Modularization <!-- 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.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> ... </dependencies> </project> 39 @nisrulz
  18. Modularization <!-- 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.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> ... </dependencies> </project> 40 @nisrulz
  19. Modularization <!-- 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.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.github.nisrulz</groupId> <artifactId>easydeviceinfo-base</artifactId> <version>2.5.0</version> <scope>compile</scope> </dependency> ... </dependencies> </project> 41 @nisrulz
  20. Modularization <!-- 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.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.android.gms</groupId> <artifactId>play-services-ads-identifier</artifactId><version>16.0.0</version> <scope>runtime</scope> </dependency> ... </dependencies> </project> 42 @nisrulz
  21. Modularization <!-- 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.5.0</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.google.android.gms</groupId> <artifactId>play-services-ads-identifier</artifactId><version>16.0.0</version> <scope>runtime</scope> </dependency> ... </dependencies> </project> 43 @nisrulz
  22. Conflict occurs between a library & app resource > Project

    will not compile What happens when... 45 @nisrulz
  23. Conflict occurs between 2 libraries integrated in the app >

    Resources from library defined first in build.gradle gets included What happens when... 46 @nisrulz
  24. Add a prefix to all your resources. How? • Enforce

    this in Android Studio Solution? 50 @nisrulz
  25. Add a prefix to all your resources. How? • Enforce

    this in Android Studio • Declare in build.gradle of library android { resourcePrefix 'YOUR_PREFIX_' // i.e 'sensey_' } Solution? 51 @nisrulz
  26. Solution? Resource named ‘app_name’ does not start with the project’s

    resource prefix ‘sensey_’; Rename to `sensey_app_name`? 52 @nisrulz
  27. 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 56 @nisrulz
  28. 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 57 @nisrulz
  29. 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 58 @nisrulz
  30. Visibility vs Organization • Code organized inside one package; everything

    is package private and only public on demand 64 @nisrulz
  31. Visibility vs Organization • Code organized inside the module i.e

    individual packages; everything is internal and only public on demand (in Kotlin land) • Drawback ◦ Need dependency on kotlin std library 65 @nisrulz
  32. Visibility vs Organization // file name: exampleLibrary.kt // module name:

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

    example package com.example.library // visible inside exampleLibrary.kt private fun setup() { ... } 67 @nisrulz
  34. Visibility vs Organization // 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{...} 68 @nisrulz
  35. Visibility vs Organization // 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" 69 @nisrulz
  36. Lifecycle Components Classes designed to help deal with Android lifecycle

    • Lifecycle • LifecycleOwner • LifecycleObserver 71 @nisrulz
  37. Lifecycle Components Classes designed to help deal with Android lifecycle

    i.e Activity, Fragment, Service, Custom 73 @nisrulz
  38. Lifecycle class MainActivity extends AppCompatActivity() {...} public class AppCompatActivity extends

    FragmentActivity{...} public class FragmentActivity extends SupportActivity { ... public Lifecycle getLifecycle() { return super.getLifecycle(); } ... } 76 @nisrulz
  39. LifecycleObserver dependencies { def lifecycleVer = "2.0.0" // Runtime implementation

    "androidx.lifecycle:lifecycle-runtime:$lifecycleVer" // Annotation Support annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycleVer" ... } 77 @nisrulz
  40. LifecycleObserver public class AwesomeLib implements LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) public void

    init() { ... } @OnLifecycleEvent(Lifecycle.Event.ON_START) public void libOnStart() { ... } ... @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) public void cleanup() { ... } } 78 @nisrulz
  41. LifecycleOwner class MainActivity : AppCompatActivity() { val awesomeLib = AwesomeLib()

    override fun onResume() { ... // Add lifecycle observer lifecycle.addObserver(awesomeLib) } override fun onStop() { ... // Remove lifecycle observer lifecycle.removeObserver(awesomeLib) } } 79 @nisrulz
  42. ProcessLifecycleOwner // Internal class to initialize Lifecycles. public class ProcessLifecycleOwnerInitializer

    extends ContentProvider { @Override public boolean onCreate() { ... ProcessLifecycleOwner.init(getContext()); return true; } ... } 93 @nisrulz
  43. ProcessLifecycleOwner Why? To invoke ProcessLifecycleOwner as soon as process starts

    Drawback? Initializes ProcessLifecycleOwner even if your app does not use it! 95 @nisrulz
  44. ProcessLifecycleOwner How to get rid of it in app? Use

    Merge rule marker in app’s AndroidManifest.xml: // Remove marked element from the merged manifest tools:node="remove" 97 @nisrulz
  45. AutoInit Android Library Android Libraries need Android context to handle

    simple tasks such as • Hook into Android Runtime • Access app resources • Use System Services • Register BroadcastReceiver 103 @nisrulz
  46. AutoInit Android Library public class MyApplication extends Application { @Override

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

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

    public void onCreate() { super.onCreate(); // Init android library MyAwesomeLibrary.init(this); } } 106 @nisrulz
  49. AutoInit Android Library ContentProvider can be used to simplify the

    process. Simply because ContentProvider • Is created and initialized (on the main thread) before all other components • Participate in manifest merging at build time. 110 @nisrulz
  50. AutoInit Android Library 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; } ... } 111 @nisrulz
  51. AutoInit Android Library 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; } ... } 112 @nisrulz
  52. AutoInit Android Library ContentProvider can be used to simplify the

    process. Challenges: • There can be only one Content Provider with a given “authority” string • Only run on main thread 118 @nisrulz
  53. AutoInit Android Library ContentProvider can be used to simplify the

    process. Why this is a bad idea: • Increases startup time • Bloats applications even when not used • Abusing functionality of ContentProvider 120 @nisrulz
  54. AutoInit Android Library ContentProvider can be used to simplify the

    process. Why this is a bad idea: • Increases startup time (Solution: async initialize) • Bloats applications even when not used • Abusing functionality of ContentProvider 121 @nisrulz
  55. AutoInit Android Library Picasso // Pre - v2.71828 Picasso.with(this).load("...<url>...").into(imageView); //

    After - v2.71828 Picasso.get().load("...<url>...").into(imageView); 128 @nisrulz
  56. AutoInit Android Library Picasso // Pre - v2.71828 Picasso.with(this).load("...<url>...").into(imageView); //

    After - v2.71828 Picasso.get().load("...<url>...").into(imageView); 129 @nisrulz
  57. Links/References 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 131 @nisrulz