Upgraded my project to Android gradle plugin 7 | Anton Danshin

Upgraded my project to Android gradle plugin 7

September 25, 2021 | 7 min read

Android Gradle Plugin requires Java 11 to run. You are currently using java 1.8.

I’ve spent several nights this week to get one of my project’s CI pipeline back up on its feet after upgrading to the latest Android gradle plugin (7.0.2). I usually try to keep versions of tools and lbraries up-to-date, but after haven’t got time to work on the project and couldn’t keep up. As a result, the app was not updated for the last 4 months – the time when Google suddenly released a bunch of updates to Android’s build tools, which broke everything…

It has never been like this and now it is exactly the same again. — Viktor Chernomyrdin

Breaking changes in Gradle 7.x

Android Gradle Plugin now shares the same major version number with Gradle, so its version jumped from 4.x to 7.0. This is a good thing, as there will be less confusion with the versions. But this version bump comes with some changes. In addition to breaking changes to the plugin APIs, which forced me to update several other third-party gradle plugins (e.g. Sentry), Android Gradle Plugin adds some requirements to the environment.

Updating to JDK 11

Previously, everything would compile with JDK 8. Now Android Gradle Plugin requires JDK 11.

An exception occurred applying plugin request [id: 'com.android.application']
> Failed to apply plugin 'com.android.internal.application'.
   > Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
     You can try some of the following options:
       - changing the IDE settings.
       - changing the JAVA_HOME environment variable.
       - changing `org.gradle.java.home` in `gradle.properties`.

On MacOS tt can be installed with Homebrew with the following command: brew install --cask adoptopenjdk11. There is also a nice article on how to enable quick switching between JDK versions.

I thought this was it, so I moved on with the development of my feature. Later, when I was ready to present an early prototype for testing, I found out that our CI server was unable to build the app. All because CI was trying to build it with JDK 8. Luckily, we run our Android builds inside a docker container. And I thought to myself:

This where docker will show it’s power! All I need to do is to replace package openjdk-8-jdk with openjdk-11-jdk in my Dockerfile, and everything should work, just like on my local machine.

Build failed again when running Android sdkmanager.

Step 13/18 : RUN yes | sdkmanager --licenses
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
   at com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:156)
   at com.android.repository.api.SchemaModule.<init>(SchemaModule.java:75)
    at com.android.sdklib.repository.AndroidSdkHandler.<clinit>(AndroidSdkHandler.java:81)
    at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:73)
    at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:48)
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
   at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
    ... 5 more
The command '/bin/sh -c yes | sdkmanager --licenses' returned a non-zero code: 1

It looks like when I put together this build script more than a year ago, I probably used some outdated sources of information. It used deprecated SDK tools, which apparently doesn’t work with Java 11 and haven’t been updated for a long time. I’ve spent quite some time trying to make it work before I realized that this was the dead end and I had to move on to the new Android command-line tools.

My new Dockerfile with Android build environment for CI now looks like this:

FROM ubuntu:18.04

ENV LANG='C' LANGUAGE='en' LC_ALL='C'

ENV DEBIAN_FRONTEND=noninteractive
ENV GRADLE_HOME=/usr/bin/gradle
ENV ANDROID_SDK_ROOT=/opt/android-sdk

RUN apt-get update && apt-get install -y \
        --allow-downgrades --allow-remove-essential --allow-change-held-packages \
        expect \
        git \
        mc \
        gradle \
        unzip \
        wget curl \
        libc6-i386 lib32stdc++6 lib32gcc1 lib32ncurses5 lib32z1 \
        openjdk-11-jdk \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

RUN rm -f /etc/ssl/certs/java/cacerts; \
    /var/lib/dpkg/info/ca-certificates-java.postinst configure

ENV PATH $PATH:/opt/tools

RUN cd /opt \
    && wget --output-document=android-commandline-tools.zip \
        https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip \
    && mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools \
    && unzip android-commandline-tools.zip -d ${ANDROID_SDK_ROOT}/cmdline-tools \
    && mv ${ANDROID_SDK_ROOT}/cmdline-tools/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest \
    && rm -v android-commandline-tools.zip \
    && chown -R root.root /opt/android-sdk

ENV ANDROID_HOME "${ANDROID_SDK_ROOT}"
ENV JAVA_HOME  /usr/lib/jvm/java-11-openjdk-amd64
ENV PATH $PATH:$JAVA_HOME/bin:$ANDROID_HOME/cmdline-tools/latest/bin

RUN yes | sdkmanager --licenses
RUN sdkmanager --update

ADD packages.txt $ANDROID_SDK_ROOT

RUN yes | sdkmanager --package_file=$ANDROID_SDK_ROOT/packages.txt

ENV PATH $PATH:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools

Broken release build

So, I finally had an environment on my CI that can build my app. But when it tried to make a release apk, it failed again.

Broken release apk build on CI

The error now was in Sentry gradle plugin:

> Task :app:addSentryProguardSettingsForRelease FAILED

FAILURE: Build failed with an exception.

* What went wrong:
A problem was found with the configuration of task ':app:addSentryProguardSettingsForRelease' (type 'SentryProguardConfigTask').
  - Type 'io.sentry.android.gradle.SentryProguardConfigTask' property 'applicationVariant' is missing an input or output annotation.

    Reason: A property without annotation isn't considered during up-to-date checking.

    Possible solutions:
      1. Add an input or output annotation.
      2. Mark it as @Internal.

Apparentrly, during last few months technologies made a big step forward. Gradle has removed some deprecated plugin API, used by older version of Sentry plugin. Sentry also released a bunch of updates that I missed.

The annoying par was that the error only showed itself during compilation of release type. Lint also didn’t alarm me about presence of a more recent version of the plugin. Anyway, updating sentry-android-gradle-plugin from 1.7.36 to latest 2.1.4 fixed the problems.

Fixing R8 full mode

After updating CI environment, I finally was able to build a proper release APK. However, when I run the app, it just crashed.

There was a problem with R8 full mode, which appeared after update. Deobfuscated stacktrace looked like this:

Caused by java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
    at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:46)
    at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:39)
    at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:202)
    at retrofit2.Retrofit.validateServiceInterface(Retrofit.java:189)
    at retrofit2.Retrofit.create(Retrofit.java:141)
    ...

The issue is not reproduced with android.enableR8.fullMode=false.

I almost decided to disable R8 full mode but suddenly I found a question with similar problem on StackOverflow. The answer had a bunch of ProGuard rules that work, but actually redundant.

It looks like in R8 full mode, generic type of an implicit parameter kotlin.coroutines.Continuation<T> in suspend functions gets erased. That’s why adding the following line to proguard-rules.pro solves the above error:

-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation

Updated Google Play requirements

Although I finally was able to fix my CI build pipeline and build rlease APK, the fight wasn’t over yet. Since August 2021, Google Play bumped the requirement for published apps to target Androdi SDK 31. It also now only accepts app bundles (.aab) rather than APKs. This is a smaller issue but yet another hurdle I need overcome in order to publish an update to Google Play.

Conclusion

I was not ready to spend that much time trying to make a new app release and it was a bit of a challenge. I did not expect that just after a few months of not working with the project, I would have to go through all this. I was planning to spend just one weekend to make this feature available for testing, but ended up working the evenings of the following week.

Many of these problems could have been avoided. All breaking changes in the APIs of the tools and libraries that we use do not happen overnight – they are uaually announced months and sometimes even years in advance. These announcement should not be ignored, and actions should not be put off till the last minute.

Everything works now, so not all is bad. Plus, this little experience with scripts I had was quite educational and inspiring. On the same evening that I fixed my CI build, I was also able to automate the deployment of this blog to Github Pages.


Profile picture

Written by Anton Danshin 🧑‍💻 Android developer, ☕️ Starbucks coffee addict

© 2022, Built with Gatsby