Search code examples
androidproguardclasscastexceptionmoshi

ProGuard: ClassCastException with Moshi+Retrofit


It works fine in debug mode, and release mode with ProGuard off, but not with ProGuard on, even with an empty one.

Here is the stack trace:

2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at h9.d0.f(Unknown Source:46)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at h9.y1.b(Unknown Source:39)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at h9.x1.c(Unknown Source:202)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at h9.v1.invoke(Unknown Source:160)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at $Proxy1.signUp(Unknown Source)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at k4.d.l(Unknown Source:35)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at k4.d.p(Unknown Source:8)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at k4.d.s(Unknown Source:4)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at j8.k1.f(Unknown Source:61)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at j8.b.e(Unknown Source:212)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at r4.u1.l(Unknown Source:311)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at o7.a.n(Unknown Source:33)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at g8.a1.run(Unknown Source:106)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at g8.h1.d0(Unknown Source:69)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at g8.b1.e(Unknown Source:244)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at g8.b1.a(Unknown Source:161)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at g8.n.s(Unknown Source:397)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at g8.n.p(Unknown Source:513)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at i8.r0.M(Unknown Source:1079)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at i8.k.V(Unknown Source:546)
2021-09-07 23:42:19.556 32130-32130/com.myapp.packagename W/System.err:     at i8.k.l(Unknown Source:630)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at j8.p.c(Unknown Source:51)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at j8.p.a(Unknown Source:1)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at j8.o.l(Unknown Source:11)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at o7.a.n(Unknown Source:33)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at g8.a1.run(Unknown Source:106)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at androidx.compose.ui.platform.q1.f0(Unknown Source:81)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at androidx.compose.ui.platform.q1.Z(Unknown Source:41)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at androidx.compose.ui.platform.p1.run(Unknown Source:57)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at android.os.Handler.handleCallback(Handler.java:900)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:103)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at android.os.Looper.loop(Looper.java:219)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:8668)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513)
2021-09-07 23:42:19.557 32130-32130/com.myapp.packagename W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1109)

I have tried these ProGuard configs, but none of them works.
here
here


Solution

  • Somehow this proguard-rules.pro works.

    # Add project specific ProGuard rules here.
    # You can control the set of applied configuration files using the
    # proguardFiles setting in build.gradle.
    #
    # For more details, see
    #   http://developer.android.com/guide/developing/tools/proguard.html
    
    -dontwarn kotlin.reflect.jvm.internal.**
    
    -keep class kotlin.reflect.jvm.internal.** { *; }
    
    -keep interface javax.annotation.Nullable
    
    # Uncomment this to preserve the line number information for
    # debugging stack traces.
    #-keepattributes SourceFile,LineNumberTable
    
    # If you keep the line number information, uncomment this to
    # hide the original source file name.
    #-renamesourcefileattribute SourceFile
    
    # Glide
    #-keep public class * implements com.bumptech.glide.module.GlideModule
    #-keep public class * extends com.bumptech.glide.module.AppGlideModule
    
    -keepattributes Signature,RuntimeVisibleAnnotations,AnnotationDefault
    
    -keep class com.google.android.gms.** { *; }
    -dontwarn com.google.android.gms.*
    -keep class com.google.api.client.** {*;}
    
    # JSR 305 annotations are for embedding nullability information.
    -dontwarn javax.annotation.**
    
    #### OkHttp, Retrofit and Moshi
    -dontwarn okhttp3.**
    -dontwarn retrofit2.Platform.Java8
    -dontwarn okio.**
    -dontwarn javax.annotation.**
    -keepclasseswithmembers class * {
        @retrofit2.http.* <methods>;
    }
    
    -keepclasseswithmembers class * {
        @com.squareup.moshi.* <methods>;
    }
    
    -keep @com.squareup.moshi.JsonQualifier interface *
    
    # Enum field names are used by the integrated EnumJsonAdapter.
    # values() is synthesized by the Kotlin compiler and is used by EnumJsonAdapter indirectly
    # Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi.
    -keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum {
        <fields>;
        **[] values();
    }
    
    # Keep helper method to avoid R8 optimisation that would keep all Kotlin Metadata when unwanted
    -keepclassmembers class com.squareup.moshi.internal.Util {
        private static java.lang.String getKotlinMetadataClassName();
    }
    
    -keep class kotlin.Metadata { *; }
    -keepclassmembers class kotlin.Metadata {
        public <methods>;
    }
    
    -keepclassmembers class * {
        @com.squareup.moshi.FromJson <methods>;
        @com.squareup.moshi.ToJson <methods>;
    }
    
    -keepnames @kotlin.Metadata class com.yourpackage.model.**
    -keep class com.yourpackage.model.** { *; }
    -keepclassmembers class com.yourpackage.model.** { *; }
    
    # 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 generic signature of Call, Response (R8 full mode strips signatures from non-kept items).
    -keep,allowobfuscation,allowshrinking interface retrofit2.Call
    -keep,allowobfuscation,allowshrinking class retrofit2.Response
    
    # 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
    

    The reason that causes ClassCastException, based on my deduction, is the lack of support from Moshi to the generic types. This is the data class that I highly suspect is the cause:

    @JsonClass(generateAdapter = true)
    data class ResponseHttp<T>(
        @field:Json(name = "statusCode") val statusCode: String,
        // this generic type is where went wrong
        @field:Json(name = "data") val data: T?,
    )