Upgrading essential tools in Android development often feels like navigating a complex maze, where each turn can either lead you to new efficiencies or unexpected challenges. Recently, I embarked on such a journey: migrating from Android Gradle Plugin (AGP) 7.4.1 to AGP 8.5.2 to leverage the newest features of compileSdkVersion
and targetSdkVersion
34 for our SDK deployed to Nexus.
The Starting Point
Choosing to upgrade to AGP 8.5.2 was based on careful research, noting that the incremental changes from AGP 8.0 to 8.5.2 wouldn’t adversely impact our project. With an optimistic outlook, I updated our SDK to comply with SDK 34 and embraced AGP 8.5.2, expecting a smooth transition. However, every update has its hurdles, and mine was just around the corner.
The Initial Setback
After updating our SDK and integrating it into a test project, I faced an unexpected error — a NoMatchingGraphVariantsException
. The artifact was right there in Nexus, visible yet unreachable by my sample app. It was frustrating, seeing the target but unable to reach it due to what initially seemed like an invisible barrier.
Caused by: org.gradle.internal.component.NoMatchingGraphVariantsException: No matching variant of com.my.sdk:1.2.3-qa was found. The consumer was configured to find a library for use during runtime, preferably optimized for Android, as well as attribute 'com.android.build.api.attributes.AgpVersionAttr' with value '8.5.2', attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' but:
- Variant 'qaApiElements-published' declares a library, as well as attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm':
- Incompatible because this component declares a component for use during compile-time, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'qa' and the consumer needed a component for use during runtime, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug'
- Other compatible attributes:
- Doesn't say anything about com.android.build.api.attributes.AgpVersionAttr (required '8.5.2')
- Doesn't say anything about its target Java environment (preferred optimized for Android)
The real issue stemmed from the nuanced way AGP 8.5.2 handles build variants. The sample app’s build system was strictly looking for matching SDK build variants — release with release, debug with debug. However, our SDK included a QA build not mirrored in the sample app, leading to a mismatch.
Navigating the Build Variants
The fix was straightforward yet crucial: instructing the build system to match the sample app’s debug build with the SDK’s QA build. This was achieved by setting matchingFallbacks
in the test app’s build configuration:
android {
buildTypes {
debug {
matchingFallbacks = ['qa']
}
}
}
This small tweak was like finding the right key for a previously locked door, allowing the app to correctly link to the intended SDK variant.
Encountering the Subtle Beast: R8
Just when I thought the path was clear, another issue surfaced. Although the app now built successfully with the SDK, it malfunctioned at runtime. The network layer, crucial for the app’s operation, was failing silently. This was a trickier problem, as there were no crashes to pinpoint the issue directly.
The root cause was tied to updates in R8 obfuscation introduced with the new AGP version. These changes had subtly modified how R8 processed our network-related code. It was a challenging issue to diagnose because the symptoms were not straightforward.
Implementing a Robust Solution
To resolve this, I updated our obfuscation rules to align with the latest specifications for Gson, Retrofit, and OkHttp. These new rules were meticulously sourced from their respective official repositories, ensuring they were both accurate and effective. Here is what I’ve added to my proguard-rules.pro file:
Job Offers
NOTE: You can look for the phrase “copied from:” in the ProGuard file to find the source of the rules.
# From Gradle 8 Onward
##---------------Begin: proguard configuration for Gson ----------
# copied from: https://github.com/google/gson/blob/main/examples/android-proguard-example/proguard.cfg
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature
# For using GSON @Expose annotation
-keepattributes *Annotation*
# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { <fields>; }
# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer
# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken
##---------------End: proguard configuration for Gson ----------
##---------------Begin: proguard configuration for Retrofit ----------
# copied from: https://github.com/square/retrofit/blob/trunk/retrofit/src/main/resources/META-INF/proguard/retrofit2.pro
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Keep annotation default values (e.g., retrofit2.http.Field.encoded).
-keepattributes AnnotationDefault
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
# Keep inherited services.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface * extends <1>
# With R8 full mode generic signatures are stripped for classes that are not
# kept. Suspend functions are wrapped in continuations where the type argument
# is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# R8 full mode strips generic signatures from return types if not kept.
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
# With R8 full mode generic signatures are stripped for classes that are not kept.
-keep,allowobfuscation,allowshrinking class retrofit2.Response
##---------------End: proguard configuration for Retrofit ----------
##---------------Begin: proguard configuration for Okhttp ----------
# copied from: https://raw.githubusercontent.com/square/okhttp/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
# A resource is loaded with a relative path so the package of this class must be preserved.
-adaptresourcefilenames okhttp3/internal/publicsuffix/PublicSuffixDatabase.gz
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
-dontwarn org.bouncycastle.**
-dontwarn org.openjsse.**
##---------------End: proguard configuration for Okhttp ----------
-keep class retrofit.** { *; }
-keep class * implements java.io.Serializable { *; }
-keep class kotlin.Metadata
With these updates, our network communications were not just restored; they were enhanced to be more robust than before.
Lessons Learned and Looking Forward
This journey taught me the importance of understanding every component of our development environment in-depth. The transition from AGP 7.4.1 to 8.5.2, while challenging, was a valuable learning experience that improved both our SDK and my skills as a developer.
Embracing changes in our tools and adapting to their evolving landscapes is crucial for staying ahead. As I continue to develop and refine our SDK, the lessons learned from this upgrade will undoubtedly serve as a guide for future enhancements
This article is previously published on proandroiddev.com