Android Kotlin ProGuard Rules Wrong?

Viewed 1162

Currently I have a library that is to be consumed by multiple other projects, however I have a problem with the code obfuscation when consuming the .aar in particular with some classes:

This one to handle backend responses or asynchronous tasks, etc.

sealed class Result<out T : Any> {
    open class Success<out T : Any>(val data: T) : Result<T>()
    open class Error(val error: ErrorModel) : Result<Nothing>()
}

And this one to act as a initializer or something like it to the library:

class LibApp private constructor(
    val appContext: Context
) {

    companion object {
        @JvmStatic lateinit var instance: LibApp
            private set

        fun init(
            appContext: Context
        ) {
            if (this::instance.isInitialized.not()) {
                instance = LibApp(appContext)
            }
        }
    }
}

The rules I'm using are:

#noinspection ShrinkerUnresolvedReference
-keep class com.cross.project.compilation.testlib.response.** { *; }
-keep class com.cross.project.compilation.testlib.LibApp.** { *; }

Currently I have 3 build types and 2 flavors:

buildTypes {
        release {
            debuggable false
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }

        releaseDebug{
            debuggable true
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }

        debug{
            debuggable true
            minifyEnabled false
        }
    }

    flavorDimensions "libFlavors"

    productFlavors {

        dev {

        }

        prod {

        }

    }

When I check the decompiled sources for release variants in the consumer project I get:

Result gets the < T > stripped from the Success return:

public sealed class Result<out T : kotlin.Any> private constructor() {
    public open class Error public constructor(error: com.cross.project.compilation.testlib.response.ErrorModel) : com.cross.project.compilation.testlib.response.Result {
        public final val error: com.cross.project.compilation.testlib.response.ErrorModel /* compiled code */
    }

    public open class Success<out T : kotlin.Any> public constructor(data: T) : com.cross.project.compilation.testlib.response.Result {
        public final val data: T /* compiled code */
    }
}

This changes the way something is coded within the variants, from something like this in dev:

 return when (val response = accountService.getAccountData()) {
            is Result.Success -> ConsumerResult.Success(response.data)
            is Result.ErrorModel -> ConsumerResult.Failure(response.errorModel)
        }

To this:

 return when (val response = accountService.getAccountData()) {
            is Result.Success<*> -> ConsumerResult.Success((response.data as AccountData))
            is Result.ErrorModel -> ConsumerResult.Failure(response.errorModel)
        }

The LibApp class gets removed for some reason.

I've tried to modify the rules to avoid these issues, but only succeding in keeping the LibApp class by applying the following:

-keepclasseswithmembers class com.cross.project.compilation.testlib.LibApp {
    public *;
}

-keep @interface kotlin.Metadata {
  *;
}
-keepattributes RuntimeVisibleAnnotations

And also add the @Keep annotation to every level in the class like:

@Keep class LibApp private constructor(
    ...

    @Keep
    companion object {
       ...

        @Keep
        fun init(
        ...

However I've had no luck modifying the rule to keep the generic return of the Result.Success, Any idea of what I'm doing wrong?

As an additional information, I'm using and building with the maven-publish plugin:

Android Gradle Plugin 4.0.1 Gradle Wrapper 6.1.1

This problem seems to have appeared after I upgraded from:

Android Gradle Plugin 3.6.3 Gradle Wrapper 5.6.4

1 Answers

The type Nothing in the Result.Error is erased while obfuscating by R8. Try to add in build.gradle file the following:

buildscript {
     repositories {
       // other repos are omitted 
        maven { url  'https://storage.googleapis.com/r8-releases/raw' }
    }
    dependencies {
        classpath 'com.android.tools:r8:2.1.68'
        // other dependencies are omitted 

In proguard file declare the Result type to keep from obfuscation

-keep class [path_to_class].Result { *; }
-keep class [path_to_class].Result$** { *; }
Related