Share
Embed
Transcript
97 slides
-
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 -
private internal importance protected public App App App API Visibility
Visibility modifiers -
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 -
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 -
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 -
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 -
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 -
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 -
@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 -
How can I effectively manage the visibility modifiers of thousands
of classes, functions, and properties? Explicit API Mode -
API lifecycles Compatibility Created Deprecated (warning) Removed Deprecated (error) v4.2.10
v4.2.20 v4.2.21 v4.3.0 -
API lifecycles Compatibility Created Deprecated (warning) Removed Deprecated (error) v4.2.10
v4.2.20 v4.2.21 v4.3.0 -
API lifecycles Compatibility Created Deprecated (warning) Removed Deprecated (error) v4.2.10
v4.2.20 v4.2.21 v4.3.0 Client -
API lifecycles Compatibility Created Deprecated (warning) Removed Deprecated (error) v4.2.10
v4.2.20 v4.2.21 v4.3.0 Client Client -
Is there any way to detect public API changes? Binary
Compatibility -
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. -
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 -
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 -
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 -
/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 AAR structure -
/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. -
<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 -
<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 -
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) -
?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/> -
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. -
Minimizing Exposed Resources Resource prefix build.gradle Applying a consistent naming
convention across all resources in your package. -
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. -
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 -
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. -
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 -
:app :library :material (MDC) com.my.library.R (:material) (:library) com.google.android.material.R (:material) implementation
implementation Transitive Dependency Transitive R class -
: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 -
: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 -
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 -
Dex format has 64K limit for methods and fields references!
64K 65,536 64 1024 2^10 Transitive Dependency Multidex problem -
android.nonTransitiveRClass=true gradle.properties Transitive Dependency Non transitive R class If you
use AGP 8.0, non transitive R class is enabled by default -
: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 -
Before After (enabled non transitive R class) Transitive Dependency Non
transitive R class -
:app :feature1 :feature2 :feature3 com.my.app.R (:feature1) (:feature2) (:feature3) (:app) Unresolved
reference! Transitive Dependency Non transitive R class -
:app :feature1 :feature2 :feature3 com.my.app.R (:feature1) (:feature2) (:feature3) (:app) full
name package import with namespacing Transitive Dependency Non transitive R class -
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)