Skip to content

Instantly share code, notes, and snippets.

@codejockie
Created April 5, 2025 18:37
Show Gist options
  • Save codejockie/608af4d3391f70cb4ebb123566ef369e to your computer and use it in GitHub Desktop.
Save codejockie/608af4d3391f70cb4ebb123566ef369e to your computer and use it in GitHub Desktop.
R8_Error.txt
java.lang.IllegalArgumentException: Unable to create call adapter for y8.d<t4.e>
for method g.a
at y8.U.n(Unknown Source:46)
at y8.p.b(Unknown Source:2850)
at y8.M.invoke(Unknown Source:67)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy2.a(Unknown Source)
at j4.h0.s(Unknown Source:30)
at j4.h0.h(Unknown Source:12)
at t6.a.f0(Unknown Source:4)
at J7.D.F(Unknown Source:77)
at y4.f.a(Unknown Source:13)
at D4.c.s(Unknown Source:201)
at h6.a.k(Unknown Source:8)
at J7.L.run(Unknown Source:114)
at G3.c.run(Unknown Source:413)
at Q7.j.run(Unknown Source:2)
at Q7.a.run(Unknown Source:93)
Suppressed: O7.d: [v0{Cancelling}@1ecdfed, Dispatchers.IO]
Caused by: java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
at t4.f.a(Unknown Source:122)
at y8.N.a(Unknown Source:32)
at y8.p.b(Unknown Source:2689)
... 14 more
@codejockie
Copy link
Author

codejockie commented Apr 5, 2025

I looked into this further and got it resolved. However, now I get a different error "Unable to invoke no-args constructor for class u4.q. Registering an InstanceCreator with Gson for this type may fix this problem."

This error happens at the point of converting response body to the desired type. I am using a custom Retrofit Call Adapter for this. Also, it a sealed class to handle the response, it looks like this:

sealed class ApiResult<out R> {
    data object Loading : ApiResult<Nothing>()
    data object NetworkError : ApiResult<Nothing>()
    data class Success<out T>(val data: T?) : ApiResult<T>()
    data class Failure(val error: WKHttpError?) : ApiResult<Nothing>()
}

@codejockie
Copy link
Author

On further investigation, I found that the ApiResult class isn't the problem as I already added it in the proguard rule. It was the response model that was the issue. I didn't have it in the proguard rule.
I find that behaviour strange because shouldn't a model be included by R8 during obfuscation?

@Skaldebane
Copy link

Skaldebane commented Apr 5, 2025

Alright, just took another look at the apk file, and I think I understand what's going on.

In my initial answer I alluded to the fact that kotlinx.serialization doesn't usually need to add any ProGuard/R8 rules (in fact, even those I mentioned aren't necessary, they all come bundled).

However, it seems like you're using Gson for JSON deserialization, given that's the only Retrofit adapter I can find in the apk:
image

The problem with Gson is that it's entirely based on reflection (inspecting classes at runtime, based on their names/packages), and R8 completely breaks that, unless you tell it to keep some stuff intact in ProGuard rules. The issue here is that there's no easy way to say "keep all classes used by Gson", because those classes look like any other class, with no sign that they're meant for serialization.

Libraries such as kotlinx.serialization use code generation alongside reflection to generate the deserialization code on the class itself, directly using its fields and constructors, which makes R8 aware of some of that, as well as incredibly easy for them to bundle a single rule that prevents R8 from touching other stuff it's not aware of, with no extra work from us. Moshi is another library with this ability (there's a reflection-only version, so you should use the code-gen one).

Anyhow, the most straightforward for now is to add ProGuard rules for your model classes.
The easiest way is to add a wildcard keep rule for the package containing them (e.g. com.codejockie.wani.model/response/etc...). Add a keep rule only for the necessary packages and classes/interfaces (the ones you're converting into/from JSON).

You seem to have things neatly organized in packages, so this should be very straightforward. Example:

-keep class your.package.name.models.** { *; }
-keep interface your.package.name.services.** { *; }

@Skaldebane
Copy link

On the long run though, I'd recommend you switch to one of those other libraries that use code-gen (I believe both have Retrofit adapters). I personally prefer kotlinx.serialization, as it's tailor-made for Kotlin, extremely simple to set up and use, and is made and maintained by Jetbrains and Kotlin team themselves.

@codejockie
Copy link
Author

That makes sense! I did just that and got the app working without crashing and response being handled correctly. I think I am going to make the switch to kotlinx.serialization and remove dependence on GSON.

@Skaldebane
Copy link

Glad that worked! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment