In my current Kotlin Android project I just had to face the following problem:
For this class hierarchy
abstract class MyClass {
...
}
class MySubClassA: MyClass() {
...
}
class MySubClassB: MyClass() {
...
}
class MySubClassC: MyClass() {
...
}
I wrote a creator function like this:
private fun <T : MyClass> createMyClass(myClassType: KClass<T>): T? {
val constructor = myClassType.primaryConstructor
return constructor?.call()?.apply {
...
}
}
This worked nicely for the debug version but for the release version with R8 obfuscation turned on the constructor was alway null. My settings for R8 were this:
-keep class kotlin.** { *; }
-keep class kotlin.Metadata { *; }
-dontwarn kotlin.**
-keepclassmembers class **$WhenMappings {
<fields>;
}
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
-assumenosideeffects class kotlin.jvm.internal.Intrinsics {
static void checkParameterIsNotNull(java.lang.Object, java.lang.String);
}
-keep class kotlin.reflect.jvm.internal.impl.builtins.BuiltInsLoaderImpl
After some reverse engineering of the Kotlin reflection API I came up with the following workaround:
private fun <T : MyClass> createMyClass(myClassType: KClass<T>): T? {
val constructor = myClassType.constructors.firstOrNull() {
it.valueParameters.isEmpty()
}
return constructor?.call()?.apply {
...
}
}
This seems to indicate, that the constructor information of the class is preserved by R8 but the meta information about a constructor being primary gets lost.
Does anybody know how to solve this in general?
I came across this issue and was able to resolve it using the #Keep annotation.
abstract class MyClass {
}
class MySubClassA #Keep constructor(): MyClass() {
}
class MySubClassB #Keep constructor(): MyClass() {
}
class MySubClassC #Keep constructor(): MyClass() {
}
Related
The project working well without enabling proguard, the problem comes when enable it, I am using retrofit with Moshi converter and Coroutines to fetch list of data, and Hilt for DI, and I added all rules and kept all models
This is the error:
Could not compute caller for function: public constructor MovieListEntity(movie_data: kotlin.collections.List<com...domain.entities.MovieData>) defined in com...domain.entities.MovieListEntity[c#dad1eb0] (member = null)
And these are the classes mentioned
data class MovieListEntity(
#field:Json(name = "movie_data")
val movie_data: List<MovieData>
)
data class MovieData(
#field:Json(name = "movie_id")
val movie_id: Int,
#field:Json(name = "sub_title")
val sub_title: String,
#field:Json(name = "title")
val title: String
)
Note: I tried also without annotations, and it didn't help
These are the proguard rules:
-keep class com.***.***.domain.entitie.** { *; }
-keep class com.***.***.domain.entities.*
-keep class com.***.***.domain.entities.MovieListEntity
-keep class com.***.***.domain.entities.MovieData
-keep class com.***.***.DataBinderMapperImpl { *; }
-keep class com.***.***.DataBinderMapperImpl { *; }
-keep class com.***.*****{
public ** component1();
<fields>;
}
Plus other rules for retrofit, OkHttp, hilt .. etc.
How Can I solve this error?
Solved by adding these rules
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
-keepclassmembers class * {
#com.squareup.moshi.FromJson <methods>;
#com.squareup.moshi.ToJson <methods>;
}
-keepnames #kotlin.Metadata class com.******.domain.entities.**
-keep class com.******.domain.entities.** { *; }
-keepclassmembers class com.*****.domain.entities.** { *; }
The problem was in Moshi library, check this
I am working on native Android app with Kotlin.
I implement a Worker class to run async webservice call.
My worker works fine unless I don't build with dexguard.
With dexguard builds only I get following error
2021-12-07 18:16:46.085 25839-29600/? E/WM-WorkerFactory: Could not instantiate com.xyz.XYZWorker
java.lang.NoSuchMethodException: com.xyz.XYZWorker.<init> [class android.content.Context, class androidx.work.WorkerParameters]
at java.lang.Class.getConstructor0(Class.java:2332)
at java.lang.Class.getDeclaredConstructor(Class.java:2170)
at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(:95)
at androidx.work.impl.WorkerWrapper.runWorker(:244)
at androidx.work.impl.WorkerWrapper.run(:136)
at androidx.work.impl.utils.SerialExecutor$b.run(:91)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
Here is my code, I mask some classnames because it is client project. I inject use cases with Hilt.
#HiltWorker
class XYZWorker #AssistedInject constructor(
#Assisted context: Context,
#Assisted workerParameters: WorkerParameters,
val someUseCase: SomeUseCase,
val someUseCase2: SomeUseCase2,
) : CoroutineWorker(context, workerParameters) {
...
}
Use cases provided by inject
class SomeUseCase #Inject constructor(
private val someRepo: SomeRepo
) : UseCaseParams<List<SomeDomainEntity>, SomeResponseDomainEntity> {
...
}
class MyWorkManagerInitializer : Initializer<WorkManager> {
override fun create(context: Context): WorkManager {
val workerFactory = getWorkerFactory(appContext = context.applicationContext)
val config = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
WorkManager.initialize(context, config)
return WorkManager.getInstance(context)
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> = mutableListOf()
private fun getWorkerFactory(appContext: Context): HiltWorkerFactory {
val workManagerEntryPoint = EntryPointAccessors.fromApplication(
appContext,
WorkManagerInitializerEntryPoint::class.java
)
return workManagerEntryPoint.hiltWorkerFactory()
}
#InstallIn(SingletonComponent::class)
#EntryPoint
interface WorkManagerInitializerEntryPoint {
fun hiltWorkerFactory(): HiltWorkerFactory
}
}
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove" />
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.xyz.MyWorkManagerInitializer"
android:value="#string/androidx_startup" />
</provider>
-keep public class androidx.startup.** { *; }
-keep public class androidx.work.** { *; }
-keep public class androidx.hilt.work.** { *; }
-keep #interface androidx.hilt.work.** { *; }
-keep #interface dagger.assisted.** { *; }
-keep #interface dagger.assisted.** { *; }
-keep public class * implements androidx.startup.Initializer { *; }
-keep interface com.xyz.WorkManagerInitializerEntryPoint { *; }
-keep public class * implements com.xyz.WorkManagerInitializerEntryPoint { *; }
-keep public class * extends androidx.work.CoroutineWorker { *; }
-keepclassmembers public class * extends androidx.work.CoroutineWorker {
public <init>(...);
#com.google.inject.Inject <init>(...);
#javax.inject.Inject <init>(...);
}
-keep public class * extends androidx.work.Worker { *; }
-keepclassmembers public class * extends androidx.work.Worker {
public <init>(...);
#com.google.inject.Inject <init>(...);
#javax.inject.Inject <init>(...);
}
-keep public class com.xyz.SomeUseCase { *; }
-keep public class com.xyz.SomeUseCase2 { *; }
and here is my libraries
implementation androidx.hilt:hilt-work:1.0.0-alpha03
implementation androidx.work:work-runtime-ktx:2.5.0
kapt androidx.hilt:hilt-compiler:1.0.0-alpha03
I use Moshi for JSON conversions.
I have a custom adapter for converting LocalDateTime to JSON:
class LocalDateTimeAdapter {
#ToJson
fun toJson(value: LocalDateTime): String {
return value.toString()
}
#FromJson
fun fromJson(value: String): LocalDateTime {
return LocalDateTime.parse(value)
}
}
I use it like so:
val moshi = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.add(LocalDateTimeAdapter())
.build()
val jsonAdapter = moshi.adapter(MyModel::class.java)
val json = jsonAdapter.toJson(model)
This works fine for the debug build variant. The application crashes for other build variants where debugging is disabled with the following exception.
kotlin.reflect.jvm.internal.KotlinReflectionInternalError: Could not compute caller for function: public constructor
I've put in hours trying to resolve this but to no avail. As far as I understand this is because of proguard. I've tried putting the following in the proguard file:
-keepclasseswithmembers class * {
#com.squareup.moshi.* <methods>;
}
-keep #com.squareup.moshi.JsonQualifier interface *
-dontwarn org.jetbrains.annotations.**
-keep class kotlin.Metadata { *; }
-keepclassmembers class kotlin.Metadata {
public <methods>;
}
-keepclassmembers class ** {
#com.squareup.moshi.FromJson *;
#com.squareup.moshi.ToJson *;
}
-keepclassmembers class com.squareup.moshi.internal.Util {
private static java.lang.String getKotlinMetadataClassName();
}
-keepnames #kotlin.Metadata class com.myapp.domain.model.**
-keep class com.myapp.domain.model.** { *; }
-keepclassmembers class com.myapp.domain.model.** { *; }
In my project, I am performing API call using RxJava.
Without proguard, it is running fine. But when I apply proguard It gives java.lang.AssertionError: java.lang.NoSuchFieldException: HTTP_1_0
in onError(e: Throwable) of a subscriber.
I applied -keepclassmembers enum * { *; } in my proguard to prevent obfuscation.
Api Call
fun latestPosts(): Subscription {
return service.latestPosts
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(APICallSubscriber(presenterContract, ApiIndex.POSTS))
}
Subscriber
class APICallSubscriber<T>(private val callback: BasePresenterContract,
private val apiIndex: String) : Subscriber<Response<T>>() {
override fun onCompleted() {
}
override fun onError(e: Throwable) {
Log.d("HomeTest", "${e}")
}
override fun onNext(response: Response<T>) {
val jsonObject = App.gson().toJsonTree(response).asJsonObject
val responseCode = jsonObject
.get(PayloadKeys.RAW_RESPONSE).asJsonObject
.get(PayloadKeys.CODE).asInt
Log.d("HomeTest", "$jsonObject")
val body: JsonElement? = jsonObject.get(PayloadKeys.BODY)
if (body != null) {
val responseBody = body.asJsonObject
callback.onNetworkRequestCompletedWith(responseBody, responseCode, apiIndex)
} else {
val errorBody: JsonElement? = jsonObject.get(PayloadKeys.ERROR_BODY)
callback.onNetwordRequestError(errorBody!!.asJsonObject, apiIndex)
}
}
}
I have tried different proguard rules but no result.
Please help.
In case this helps anyone, I had an existing project with a library module I had made. To prevent my app from crashing after being minified by ProGuard, I needed to add
-keepclassmembers enum * { *; }
to not only the library's build.gradle, but also to the main module's build.gradle.
I was facing the same issue, I solved by adding all these in proguard-rules.pro file:
-keepclassmembers enum * { *; }
-keep class com.google.code.gson.* { *; }
-keepattributes *Annotation*, Signature, Exception
-keepclassmembers,allowobfuscation class * {
#com.google.gson.annotations.SerializedName <fields>;
}
below configuration always worked for me
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
In Debug version it is working fine, but fails on Release version. I guess it is related to Proguard rules.
// Koin
implementation 'org.koin:koin-android:1.0.2'
implementation 'org.koin:koin-android-viewmodel:1.0.2'
Modules:
val appModule = module {
viewModel<IntroVM>()
}
val dataModule = module {
single { NetworkManager(androidContext().applicationContext) }
single { LocalFileManager(androidContext().applicationContext) }
single { ApiModel(get()) }
Class definitions:
class ApiModel(val networkManager: NetworkManager){}
class LocalFileManager(private var appContext: Context) {}
class NetworkManager(private var appContext: Context) {}
class IntroVM(var apiModel: ApiModel) : CommonVM() {
Koin init:
startKoin(this, listOf(appModule, dataModule))
Proguard rules:
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
-keep public class * extends java.lang.Exception
-keepclassmembernames class kotlinx.** {
volatile <fields>;
}
StackTrace:
Error while resolving instance for class 'com...viewModels.auth.b' -
error: org.koin.error.BeanInstanceCreationException:
Can't create definition for 'Factory [name='b',class='com..viewModels.auth.b', binds~(androidx.lifecycle.r)]' due to error :'binds~(androidx.lifecycle.r)]
No constructor found for class 'class com...viewModels.auth.b'
As you are using androidX
Use below library of viewmodel for koin
implementation 'org.koin:koin-androidx-viewmodel:1.0.2'
This issue provided needed solution