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

How to Build Awesome Android Libraries (Droidco...

How to Build Awesome Android Libraries (Droidcon Webinar)

This talk is a collection of advice about building Android libraries or SDKs, highlighting some of the less obvious things you can do to make your library great.

Many of the recommendations will also make your life easier in any multi-module project, even if you're not publishing libraries anywhere.

All of this is based on my own open-source library development work - which I do both in my free time and at my day job working on the Stream Chat Android SDK.

More info and resources: https://zsmb.co/appearances/droidcon-webinar-june-2021/

Márton Braun

June 17, 2021
Tweet

More Decks by Márton Braun

Other Decks in Technology

Transcript

  1. GetStream/stream-chat-android › Open source chat SDK › Low-level client with

    real-time events › Offline support › Pre-built UI components
  2. GetStream/stream-chat-android › Open source chat SDK › Low-level client with

    real-time events › Offline support › Pre-built UI components
  3. GetStream/stream-chat-android › Open source chat SDK › Low-level client with

    real-time events › Offline support › Pre-built UI components › Kotlin-first APIs
  4. GetStream/stream-chat-android › Open source chat SDK › Low-level client with

    real-time events › Offline support › Pre-built UI components › Kotlin-first APIs › Free for makers!
  5. TL;DW Minimize ALL the things! › Features of the library

    › Public API › Dependencies › Performance impact
  6. TL;DW Minimize ALL the things! › Features of the library

    › Public API › Dependencies › Performance impact › Enabled features by default
  7. TL;DW Minimize ALL the things! › Features of the library

    › Public API › Dependencies › Performance impact › Enabled features by default › Requirements
  8. Public API › Minimal API › Deprecations › Easy to

    use › Hard to misuse › Controlling visibility › Validating
  9. Public API › Minimal API › Deprecations › Easy to

    use › Hard to misuse › Controlling visibility › Validating
  10. Public API › Minimal API › Deprecations › Easy to

    use › Hard to misuse › Controlling visibility › Validating
  11. Public API › Minimal API › Deprecations › Easy to

    use › Hard to misuse › Controlling visibility › Validating
  12. Public API › Minimal API › Deprecations › Easy to

    use › Hard to misuse › Controlling visibility › Validating › Dependencies interface ChatClient { fun sendMessage(message: Message): Message }
  13. Public API › Minimal API › Deprecations › Easy to

    use › Hard to misuse › Controlling visibility › Validating › Dependencies interface ChatClient { suspend fun sendMessage(message: Message): Message }
  14. Public API › Minimal API › Deprecations › Easy to

    use › Hard to misuse › Controlling visibility › Validating › Dependencies interface ChatClient { fun sendMessage(message: Message): Single<Message> }
  15. Public API › Minimal API › Deprecations › Easy to

    use › Hard to misuse › Controlling visibility › Validating › Dependencies public interface Call<T> { public fun execute(): Result<T> public fun enqueue(callback: Callback<T>) } public suspend fun <T> Call<T>.await(): Result<T>
  16. Dependencies Install conversion rate increase per 10MB decrease in APK

    size by select market (Google internal data, late 2017) › Minimal dependencies › Size
  17. Dependencies › Minimal dependencies › Size › Many libraries per

    app Install conversion rate increase per 10MB decrease in APK size by select market (Google internal data, late 2017)
  18. Dependencies › Minimal dependencies › Size › Many libraries per

    app › Library present in multiple apps › Complexity
  19. Dependencies › Minimal dependencies › Size › Many libraries per

    app › Library present in multiple apps › Complexity › Version conflicts
  20. Dependencies Library 2 Library 1 Appcompat 1.4 alpha Appcompat 1.2

    App › Minimal dependencies › Size › Many libraries per app › Library present in multiple apps › Complexity › Version conflicts
  21. Dependencies › Minimal dependencies › Size › Many libraries per

    app › Library present in multiple apps › Complexity › Version conflicts Library 2 Library 1 Appcompat 1.4 alpha Appcompat 1.2 App
  22. Dependencies › Minimal dependencies › Size › Many libraries per

    app › Library present in multiple apps › Complexity › Version conflicts Library 1 Appcompat 1.4 alpha Appcompat 1.2 App Library 2
  23. The POM file library.pom <?xml version="1.0" encoding="UTF-8"?> <project> <groupId>io.getstream</groupId> <artifactId>stream-chat-android-client</artifactId>

    <version>4.12.0</version> <packaging>aar</packaging> <name>stream-chat-android-client</name> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>1.5.10</version> <scope>runtime</scope> </dependency> ... </dependencies> </project> library.aar resources layout.xml values.xml AndroidManifest.xml classes.jar R.txt library- sources.jar library- javadoc.jar *.kt *.html
  24. The POM file library.pom <?xml version="1.0" encoding="UTF-8"?> <project> <groupId>io.getstream</groupId> <artifactId>stream-chat-android-client</artifactId>

    <version>4.12.0</version> <packaging>aar</packaging> <name>stream-chat-android-client</name> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>1.5.10</version> <scope>runtime</scope> </dependency> ... </dependencies> </project>
  25. The POM file library.pom <?xml version="1.0" encoding="UTF-8"?> <project> <groupId>io.getstream</groupId> <artifactId>stream-chat-android-client</artifactId>

    <version>4.12.0</version> <packaging>aar</packaging> <name>stream-chat-android-client</name> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>1.5.10</version> <scope>runtime</scope> </dependency> ... </dependencies> </project>
  26. The POM file library.pom <?xml version="1.0" encoding="UTF-8"?> <project> <groupId>io.getstream</groupId> <artifactId>stream-chat-android-client</artifactId>

    <version>4.12.0</version> <packaging>aar</packaging> <name>stream-chat-android-client</name> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>1.5.10</version> <scope>runtime</scope> </dependency> ... </dependencies> </project>
  27. Fat AARs "At the moment we are talking with Gradle

    about being able to merge dependencies from multiple projects (that are being bundled), as that part is a problem in the JVM ecosystem as well. Once that is in, we can focus on the Android-specific features (Android resources, manifest etc.)." 2020-12-02
  28. Startup impact › Don’t hog startup › Competing with other

    libraries › Initialize on-demand › App Startup
  29. Logging › Don’t log by default › Don’t enable any

    feature by default val client = ChatClient.Builder("apiKey", context) .logLevel(ChatLogLevel.ALL) .build()
  30. Permissions & features <?xml version="1.0" encoding="utf-8"?> <manifest> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"

    /> <uses-feature android:name="android.hardware.bluetooth" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> <application> <provider /> <activity /> <activity /> </application> </manifest> library.aar resources layout.xml values.xml AndroidManifest.xml classes.jar R.txt
  31. Permissions & features <?xml version="1.0" encoding="utf-8"?> <manifest> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"

    /> <uses-feature android:name="android.hardware.bluetooth" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> <application> <provider /> <activity /> <activity /> </application> </manifest> AndroidManifest.xml
  32. <?xml version="1.0" encoding="utf-8"?> <manifest> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" /> <uses-feature android:name="android.hardware.bluetooth"

    /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> <application> <provider /> <activity /> <activity /> </application> </manifest> Permissions & features AndroidManifest.xml
  33. <?xml version="1.0" encoding="utf-8"?> <manifest> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" /> <uses-feature android:name="android.hardware.bluetooth"

    /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> <application> <provider /> <activity /> <activity /> </application> </manifest> Permissions & features AndroidManifest.xml
  34. <?xml version="1.0" encoding="utf-8"?> <manifest> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29" /> <uses-feature android:name="android.hardware.bluetooth"

    /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CAMERA" /> <application> <provider /> <activity /> <activity /> </application> </manifest> Permissions & features AndroidManifest.xml
  35. Resource prefixes android { resourcePrefix 'stream_ui_' } <?xml version="1.0" encoding="utf-8"?>

    <resources> <color name="stream_ui_accent_blue">#005FFF</color> <color name="stream_ui_accent_red">#FF3742</color> <color name=" ">#20E070</color> </resources> accent_green
  36. Resource prefixes android { resourcePrefix 'stream_ui_' } <?xml version="1.0" encoding="utf-8"?>

    <resources> <color name="stream_ui_accent_blue">#005FFF</color> <color name="stream_ui_accent_red">#FF3742</color> <color name=" ">#20E070</color> </resources> accent_green Resource named 'accent_green' does not start with the project's resource prefix 'stream_ui_'; rename to 'stream_ui_accent_green' ?
  37. Resource prefixes android { resourcePrefix 'stream_ui_' } <?xml version="1.0" encoding="utf-8"?>

    <resources> <declare-styleable name="MessageInputView"> <attr name="streamUiAttachButtonEnabled" format="boolean" /> <attr name="streamUiAttachButtonIcon" format="reference" /> <attr name="streamUiMentionsEnabled" format="boolean" /> <attr name="streamUiMentionsIcon" format="reference" /> <attr name="streamUiAttachmentMaxFileSizeMb" format="integer" /> </declare-styleable> </resources>
  38. Proguard ## Stream Chat Android Client Proguard Rules -keep class

    io.getstream.chat.android.client.api.* { *; } -keep class io.getstream.chat.android.client.api.models.* { *; } -keep class io.getstream.chat.android.client.api2.model.** { *; } -keep class io.getstream.chat.android.client.errors.* { *; } -keep class io.getstream.chat.android.client.events.* { *; } -keep class io.getstream.chat.android.client.models.* { *; } -keep class io.getstream.chat.android.client.parser.* { *; } -keep class io.getstream.chat.android.client.socket.* { *; } -keep class io.getstream.chat.android.client.utils.Result { *; } -keep class io.getstream.chat.android.client.utils.SyncStatus { *; } library.aar resources layout.xml values.xml AndroidManifest.xml classes.jar R.txt proguard.txt
  39. Proguard ## Stream Chat Android Client Proguard Rules -keep class

    io.getstream.chat.android.client.api.* { *; } -keep class io.getstream.chat.android.client.api.models.* { *; } -keep class io.getstream.chat.android.client.api2.model.** { *; } -keep class io.getstream.chat.android.client.errors.* { *; } -keep class io.getstream.chat.android.client.events.* { *; } -keep class io.getstream.chat.android.client.models.* { *; } -keep class io.getstream.chat.android.client.parser.* { *; } -keep class io.getstream.chat.android.client.socket.* { *; } -keep class io.getstream.chat.android.client.utils.Result { *; } -keep class io.getstream.chat.android.client.utils.SyncStatus { *; } proguard.txt
  40. Proguard ## Stream Chat Android Client Proguard Rules -keep class

    io.getstream.chat.android.client.api.* { *; } -keep class io.getstream.chat.android.client.api.models.* { *; } -keep class io.getstream.chat.android.client.api2.model.** { *; } -keep class io.getstream.chat.android.client.errors.* { *; } -keep class io.getstream.chat.android.client.events.* { *; } -keep class io.getstream.chat.android.client.models.* { *; } -keep class io.getstream.chat.android.client.parser.* { *; } -keep class io.getstream.chat.android.client.socket.* { *; } -keep class io.getstream.chat.android.client.utils.Result { *; } -keep class io.getstream.chat.android.client.utils.SyncStatus { *; } -dontoptimize -dontshrink proguard.txt
  41. Proguard android { buildTypes { release { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt’), 'proguard-rules.pro'

    consumerProguardFiles 'consumer-proguard-rules.pro' } debug { consumerProguardFiles 'consumer-proguard-rules.pro' } } }
  42. Proguard android { buildTypes { release { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt’), 'proguard-rules.pro'

    consumerProguardFiles 'consumer-proguard-rules.pro' } debug { consumerProguardFiles 'consumer-proguard-rules.pro' } } }
  43. Proguard android { buildTypes { release { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt’), 'proguard-rules.pro'

    consumerProguardFiles 'consumer-proguard-rules.pro' } debug { consumerProguardFiles 'consumer-proguard-rules.pro' } } }
  44. Testing › Lots of responsibility › Diverse environments › Can’t

    assume anything › Devices, architectures, OS versions, stores
  45. Testing › Lots of responsibility › Diverse environments › Can’t

    assume anything › Devices, architectures, OS versions, stores › Other frameworks and libraries
  46. Testing › Lots of responsibility › Diverse environments › Can’t

    assume anything › Devices, architectures, OS versions, stores › Other frameworks and libraries › Your code is not final
  47. Community › Open source vs source available › Address issues

    and discussions › Take PR contributions
  48. Community › Open source vs source available › Address issues

    and discussions › Take PR contributions › Be open for feedback*
  49. Community › Open source vs source available › Address issues

    and discussions › Take PR contributions › Be open for feedback* “You're supposed to side with the community, but you should also have a say in what you're building” – Filip Babić
  50. Resources • Nishant Srivastava’s excellent talks  Things I wish

    I knew when I started building Android SDK/Libraries  https://www.youtube.com/watch?v=G-x9wRWwICo  Things I wish I knew when I started building Android Libraries Vol 2  https://www.youtube.com/watch?v=jQyt3HSmx2I • Mastering API Visibility in Kotlin  https://zsmb.co/talks/mastering-api-visibility/  Kotlin/binary-compatibility-validator  https://github.com/Kotlin/binary-compatibility-validator • Publishing Android libraries to MavenCentral in 2021  https://proandroiddev.com/publishing-android-libraries-to-mavencentral-in-2021-8ac9975c3e52
  51. Resources • Jeroen Mols’ library development articles  https://jeroenmols.com/blog/2020/10/28/library-gettingstarted/ 

    What is a library and how is it deployed  https://jeroenmols.com/blog/2020/11/04/library-modularization/  Publishing submodules, fat AARs, controlling access  https://jeroenmols.com/blog/2020/11/11/library-dependencies/  Dependency conflicts and version incompatibilities • More on fat AARs  Sam Edwards’ article  https://handstandsam.com/2018/07/13/why-we-need-fat-aars-for-android-libraries/  Open issue on Google’s issue tracker  https://issuetracker.google.com/issues/62121508
  52. How to Build Awesome Android Libraries zsmb.co/talks zsmb13 Márton Braun

    › Minimize all the things! › Public API › Dependencies › Size and impact › Publishing and releases › Don’t forget the docs!