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

Android Librarian's Guide: Building Robust Libr...

Avatar for Jaewoong Jaewoong
September 10, 2025
1

Android Librarian's Guide: Building Robust Libraries and SDKs

Avatar for Jaewoong

Jaewoong

September 10, 2025
Tweet

Transcript

  1. API Surfaces API (Application Programming Interface) An API is a

    set of rules and protocols that allows different software applications to communicate with each other. It defines how software components should interact. Android Project Jetpack ViewModel Jetpack Compose Firebase SDK RevenueCat SDK
  2. class ApiResponseCallAdapter constructor( val resultType: Type ) : CallAdapter<Type, Call<ApiResponse<Type>>>

    { override fun responseType(): Type { return resultType } override fun adapt(call: Call<Type>): Call<ApiResponse<Type>> { return ApiResponseCallDelegate(call) } } App API Visibility Classes
  3. internal class ApiResponseCallAdapter constructor( private val resultType: Type ) :

    CallAdapter<Type, Call<ApiResponse<Type>>> { override fun responseType(): Type { return resultType } override fun adapt(call: Call<Type>): Call<ApiResponse<Type>> { return ApiResponseCallDelegate(call) } } App API Visibility Classes
  4. fun Context.dp2Px(dp: Int): Int { val scale = resources.displayMetrics.density return

    (dp * scale).toInt() } fun Context.px2Sp(px: Int): Int { val scale = resources.displayMetrics.scaledDensity return (px / scale).toInt() } val px = context.dp2Px(dp = 22) val sp = context.px2Sp(px = 22) API Visibility Extensions
  5. internal fun Context.dp2Px(dp: Int): Int { val scale = resources.displayMetrics.density

    return (dp * scale).toInt() } internal fun Context.px2Sp(px: Int): Int { val scale = resources.displayMetrics.scaledDensity return (px / scale).toInt() } val px = context.dp2Px(dp = 22) // Unresolved reference val sp = context.px2Sp(px = 22) // Unresolved reference API Visibility Extensions
  6. internal fun Context.dp2Px(dp: Int): Int { val scale = resources.displayMetrics.density

    return (dp * scale).toInt() } internal fun Context.px2Sp(px: Int): Int { val scale = resources.displayMetrics.scaledDensity return (px / scale).toInt() } int px = ContextExtensionKt.dp2Px(context, 11); // you can still access in Java int sp = ContextExtensionKt.px2Sp(context, 11); // you can still access in Java API Visibility JvmSynthetic
  7. internal fun Context.dp2Px(dp: Int): Int { val scale = resources.displayMetrics.density

    return (dp * scale).toInt() } internal fun Context.px2Sp(px: Int): Int { val scale = resources.displayMetrics.scaledDensity return (px / scale).toInt() } API Visibility JvmSynthetic int px = ContextExtensionKt.dp2Px(context, 11); // you can still access in Java int sp = ContextExtensionKt.px2Sp(context, 11); // you can still access in Java
  8. @JvmSynthetic internal fun Context.dp2Px(dp: Int): Int { val scale =

    resources.displayMetrics.density return (dp * scale).toInt() } @JvmSynthetic internal fun Context.px2Sp(px: Int): Int { val scale = resources.displayMetrics.scaledDensity return (px / scale).toInt() } int px = ContextExtensionKt.dp2Px(context, 11); // Unresolved reference int sp = ContextExtensionKt.px2Sp(context, 11); // Unresolved reference API Visibility JvmSynthetic
  9. How can I effectively manage the visibility modifiers of thousands

    of classes, functions, and properties? Explicit API Mode
  10. Binary Compatibility What is Binary Compatibility? The strictest and the

    most important type of compatibility. It means that an app (a binary file, like an .apk or .jar) that was compiled with an old version of your library will still run correctly with a new version of your library, without needing to be recompiled. The user can just drop in the new library file, and the app won't crash on startup. In a nutshell, validating binary compatibility is all about checking if any publicly exposed signatures (classes, methods, fields) have been changed in a way that would break existing, already-compiled code that tries to use them.
  11. Binary Compatibility Metalava Google’s AndroidX Jetpack libraries & RevenueCat SDK

    are using Metalava. Metalava offers: • Excluding specific source sets • Naming for output files • Hidden annotations • Adding arguments • etc
  12. Why RevenueCat’s Android SDK uses Metalava? JetBrains Binary Compatibility Validator

    vs. Google’s Metalava • Most features are quite similar and compatible with each other. • Binary Compatibility Validator doesn’t support modules configured with product flavors. On the other hand, RevenueCat’s KMP SDK uses Binary Compatibility Validator. Binary Compatibility Metalava
  13. What is AAR file? An AAR (Android Archive) is a

    file format used by Android to package libraries for reuse across multiple projects. It is portable, containing Android-specific resources (layouts, drawables, strings, etc) unlike a jar (Java Archive) file. Minimizing Exposed Resources AAR structure mylibrary.aar revenuecat.aar jetpack.aar Android Project
  14. /classes.jar /res/ /R.txt /public.txt /assets/ /libs/name.jar /jni/abi_name/name.so /proguard.txt /lint.jar /api.jar

    AAR file Minimizing Exposed Resources res/drawable res/layout res/menu res/values res/xml res/raw res/anim res/animator res/mipmap res/font /res/ /res/values/ string.xml colors.xml styles.xml dimens.xml arrays.xml … AAR structure We’re not only packaging classes, methods, and properties.
  15. <resources> <color name="white">#FFFFFF/color> <color name="black">#000000/color> <color name="blue">#57A8D8/color> <color name="yellow">#FBC02D/color> </resources>

    Library v1.0 (colors.xml) Project val whiteColor = ContextCompat.getColor(this, R.color.white) binding.myView.setBackgroundColor(whiteColor) <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="skydoves" android:textColor="@color/yellow" /> Minimizing Exposed Resources Transitive resources
  16. <resources> <color name="white">#FFFFFF/color> <color name="black">#000000/color> <color name="blue">#57A8D8/color> <color name="yellow">#FBC02D/color> </resources>

    Library v2.0 (colors.xml) val whiteColor = ContextCompat.getColor(this, R.color.white) binding.myView.setBackgroundColor(whiteColor) <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:text="skydoves" android:textColor="@color/yellow" /> Project compile error! Minimizing Exposed Resources Transitive resources
  17. Minimizing Exposed Resources <public/> ?xml version="1.0" encoding="utf-8"? <resources xmlns:tools="http://schemas.android.com/tools"> !

    Definitions of layouts to be exposed as public ⟶ <public name="balloon_layout_body" type="layout" /> <public name="balloon_layout_overlay" type="layout" /> ! Definitions of attributes to be exposed as public ⟶ <public name="balloon_drawableStart" type="attr" /> <public name="balloon_drawableEnd" type="attr" /> <public name="balloon_drawableTop" type="attr" /> <public name="balloon_drawableBottom" type="attr" /> <public name="balloon_drawablePadding" type="attr" /> <public name="balloon_drawableTintColor" type="attr" /> <public name="balloon_drawableWidth" type="attr" /> <public name="balloon_drawableHeight" type="attr" /> <public name="balloon_drawableSquareSize" type="attr" /> Library (/res/values/public.xml)
  18. ?xml version="1.0" encoding="utf-8"? <resources xmlns:tools="http://schemas.android.com/tools"> ! Definitions of layouts to

    be exposed as public ⟶ <public name="balloon_layout_body" type="layout" /> <public name="balloon_layout_overlay" type="layout" /> ! Definitions of attributes to be exposed as public ⟶ <public name="balloon_drawableStart" type="attr" /> <public name="balloon_drawableEnd" type="attr" /> <public name="balloon_drawableTop" type="attr" /> <public name="balloon_drawableBottom" type="attr" /> <public name="balloon_drawablePadding" type="attr" /> <public name="balloon_drawableTintColor" type="attr" /> <public name="balloon_drawableWidth" type="attr" /> <public name="balloon_drawableHeight" type="attr" /> <public name="balloon_drawableSquareSize" type="attr" /> res/drawable res/layout res/menu res/values res/xml res/raw res/anim res/animator res/mipmap res/font String.xml attrs.xml colors.xml styles.xml dimens.xml arrays.xml /res/ /res/values/ Library (/res/values/public.xml) Minimizing Exposed Resources <public/>
  19. Minimizing Exposed Resources :app :library ❌ R.drawable.arrow ✅ R.drawable.arrow Transitive

    resources Transitive Resources If you create a resource with the same name, it will overwrite the original resource.
  20. Transitive Dependency :app :library :material (MDC) implementation api Transitive dependency

    :app :library A :material (MDC) implementation api Transitive dependency :library B implementation No matter how many packages are involved, your app’s compile classpath will still include the Material library as a transitive dependency.
  21. Transitive Dependency :app :library :material (MDC) implementation api Transitive dependency

    If the version of a transitive dependency differs from the version used at the endpoint, it can lead to a dependency conflict. Version 2.0 Version 1.1.1 Version 2.0 conflict
  22. Transitive Dependency AndroidX Jetpack Fragment Library Even if you only

    add the AndroidX Fragment library using ‘implementation’, it will still transitively bring in numerous dependencies to your library users. It can be super difficult for your library users to debug issues or understand why other dependency versions have changed, and suddenly those behaviors are changed, just from importing/updating your library.
  23. Transitive Dependency • If your library depends on other libraries,

    it's important to be mindful of the transitive dependencies they bring in. • Minimizing transitive dependencies is best for reducing any potential bugs. RevenueCat SDK
  24. :app :library :material (MDC) com.my.library.R (:material) (:library) com.google.android.material.R (:material) implementation

    implementation com.my.app.R (:material) (:library) (:app) Transitive Dependency Transitive R class
  25. :app :library :material (MDC) com.my.library.R (:material) (:library) com.google.android.material.R (:material) implementation

    implementation com.my.app.R (:material) (:library) (:app) Transitive R Class Transitive Dependency Transitive R class
  26. Wow, tens of thousands lines of code in R classes

    in a minute! :app :library :material (MDC) com.my.library.R (:material) (:library) com.google.android.material.R (:material) implementation implementation com.my.app.R (:material) (:library) (:app) Transitive Dependency Transitive R class
  27. Dex format has 64K limit for methods and fields references!

    64K 65,536  64  1024 2^10 Transitive Dependency Multidex problem
  28. :app :mylibrary :material (MDC) implementation implementation com.my.library.R (:mylibrary) com.google.android.material.R (:material)

    com.my.app.R (:app) Generates R classes for resources defined in the current module only. Transitive Dependency Non transitive R class nonTransitiveRclass
  29. :app :feature1 :feature2 :feature3 com.my.app.R (:feature1) (:feature2) (:feature3) (:app) full

    name package import with namespacing Transitive Dependency Non transitive R class
  30. Recap • Minimize API surfaces (visibility modifiers, @JvmSynthetic, explicit API

    mode) • Importance of API lifecycles (@Deprecated, deprecations doc) • Control API compatibilities (Binary Compatibility Validator, Metalava) • Minimize exposed resources (public tag, resourcePrefix) • Control transitive dependencies • Enable Non-transitive R class (Dex 64k limits)