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

[Devoxx Belgium '18] Things I wish I knew when ...

[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

Nishant Srivastava

November 14, 2018
Tweet

More Decks by Nishant Srivastava

Other Decks in Technology

Transcript

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

    } } dependencies { implementation(name:'nameOfYourAARFileWithoutExtension', ext:'aar') }
  2. 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
  3. Sensey’s build.gradle @nisrulz dependencies { ... // Other testing dependencies

    // Transitive dependency: Support Compat library implementation "com.android.support:support-compat:27.0.2" }
  4. 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
  5. 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>[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>
  6. 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>[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>
  7. 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>
  8. 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' } ... }
  9. 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
  10. Bundled Proguard Configs @nisrulz # To check the merged configuration

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

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

    the below in library proguard rules disables # the optimizations in the Android app -dontoptimze
  13. 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" }
  14. 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>
  15. 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>
  16. 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>
  17. 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>
  18. 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>
  19. @nisrulz Conflict occurs between a library & app resource >

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

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

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

    project’s resource prefix ‘sensey_’; Rename to `sensey_app_name`?
  23. @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
  24. @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
  25. @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
  26. 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
  27. 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
  28. Visibility vs Organization @nisrulz // file name: exampleLibrary.kt // module

    name: example package com.example.library // visible inside exampleLibrary.kt private fun setup() { ... }
  29. 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{...}
  30. 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"
  31. Lifecycle Components @nisrulz Classes designed to help deal with Android

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

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

    implementation "android.arch.lifecycle:runtime:$lifecycleVer" // Annotation Support annotationProcessor "android.arch.lifecycle:compiler:$lifecycleVer" ... }
  34. 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() { ...} }
  35. 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) } }
  36. ProcessLifecycleOwner @nisrulz dependencies { def lifecycleVer = "1.1.1" // For

    ProcessLifecycleOwner implementation "android.arch.lifecycle:extensions:$lifecycleVer" ... }
  37. ProcessLifecycleOwner @nisrulz // Source code // Internal class to initialize

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

    use it! Why? To invoke ProcessLifecycleOwner as soon as process starts
  39. 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
  40. AutoInit Android Library @nisrulz public class MyApplication extends Application {

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

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

    @Override public void onCreate() { super.onCreate(); // Init android library MyAwesomeLibrary.init(this); } }
  43. 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.
  44. 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; } ... }
  45. 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; } ... }
  46. 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>
  47. 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>
  48. 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>
  49. 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>
  50. 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
  51. 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