Dagger 2.12 and proguard issue - android

I have an error after switching from Dagger 2.5 to 2.12 when using proguard on my release build.
DaggerGraph.java:662: error: cannot find symbol
ReportingService_MembersInjector.injectA(instance, provideDataLayerProvider.get());
I have an Android Library that is compiled and obfuscated and an Android App that includes that Library.
The graph is generated using components from both modules.
Any hints?
Thanks
PS. With Dagger 2.5 it is working without problems.
PPS. The debug build without proguard is also working good with Dagger 2.12

Speculative answer: This may have less to do with Proguard and more to do with some of the optimizations made specifically in Dagger 2.12.
Because you're running Dagger on the library you're created, and then consuming that library from a different Dagger app, Dagger gets two chances to run: First for the library, which creates your ReportingService_MembersInjector, and then a second which presumably consumes that same ReportingService. Between those steps, Proguard can effectively do whatever it wants to classes that you haven't marked with -keep and related switches. My hunch is that Dagger needed to keep your injectA method as of 2.5, but the 2.12 optimizations no longer need you to keep that method, so Proguard eliminates it.
In your Android app that consumes the library, Dagger detects a class named ReportingService_MembersInjector, so it doesn't create another copy, and incorrectly assuming it contains all of the methods that it would generate.
I think the root of the problem is that your library exposes an #Inject-annotated class that your outer (app) Dagger graph is evidently consuming directly, and then you are also keeping the Factory and MembersInjector classes that Dagger provides adjacent to it. Even if you were to properly -keep your generated MembersInjector, Provider, and Factory classes, you might experience version differences between the inner obfuscated library and the outer copy of Dagger that would make for different sorts of trouble. Instead, provide a factory or other official way of creating your library class from the outside of the library, so there's no reason that the two Dagger runs can interfere with one another.

Related

Koin can't create instance of class from published library

In my android app, I had a module labelled base. Inside base were a couple classes which acted as base lifecycle classes(Controller, ViewModel, etc.). My app was working just fine with these class local inside my project. I decided to move those out into their own library so that they could be reused on future projects. So I've published my library via jitpack and now add that library as a dependency in my gradle file.
My issue is that now it seems like all the code has been obfuscated and Koin cannot find definitions for my ViewModel classes. For example, the error I get it
Caused by: h.a.c.f.e: No definition found for 'c.c.a' has been found. Check your module definitions.
I'm using ViewModel{} block to inject my view models in my koin modules but no I don't even know what classes it can not find definitions for because all the code has been obfuscated. Has anyone encountered this and can point me in right direction? The only change I made was delete the local files and publish those files to a library which I now have as a dependency.
The problem resolved itself, I have no idea how or why. I created a new release on my github and used that version and Wa-Lah.

Should I use Dagger2 in library project? Will it cause issues for application?

I am currently working on Android library project. Currently we have pure dependency injection(without any Frameworks). However, we expand our library functionality: providing more modules, separating code into new modules, dynamic delivery and etc. This is why our current DI will not suit our needs. We would need to invest tons of resources into that.
I did not used Dagger2 in the beginning, because I thought that library should have few dependencies. I am comfortable with Dagger2, so it won't create any issues.
However, what I fear is various issues, which my occur while integrating our library.
I wanted to ask, if it is possible to get Android gradle, or any other issues, if application does not use Dagger2 and library does?
Will integration issues occur if both application and library, uses different versions of dagger2?
Will integration issues occur if application uses another DI framework, like Coin for instance?
Thanks for answers!
Yes, you can use Dagger 2 for library project.
Dagger 2 is basically used to resolve dependencies into independent modules for better unit testing and various other optimisations of the project both library or user app.
No, integration problems doesn't occur if you can handle you Dex files. Also you may try using your library's files (external libraries) to avoid such errors.
No, integration error should not occur if two different external modules are used. But on must ensure thay if any common external module is being used by both DIs, both DIs must have same version of external module or well Dex managed.

Is there a way to disable usage of specific packages?

I'm on a team with multiple developers. We're using JUnit5 via android-junit5 and tests written using the #Test annotation from the org.junit package as opposed to the org.junit.jupiter.api package are excluded from gradle's test reporting. I'd like to, if possible, outright prevent developers from using org.junit. Is there a way to do this using gradle? I'd like to achieve this particular solution and not a workaround as there are other instances that we'd like to prevent users from using a given package (java.time.* in Java 8 vs ThreeTenABP)
Thank you
One way of approaching the problem could be to create a Jar that contains only the allowed APIs. You could then upload that jar to an repository and ask developers to use it as a testCompileOnly dependency while keeping the original jar as a testRuntimeOnly dependency.
This will guarantee that test code cannot access the forbidden classes/packages since it will not see them during compilation.

Proguard stripping out implements interface. Causing java.lang.IncompatibleClassChangeError at runtime

After updating dependencies to latest versions we ran into a Runtime crash in release builds because of missing interface implementation
java.lang.IncompatibleClassChangeError:
Class 'com.mypackage.app.data.cache.query.user.QueryUserFollowersCountById'
does not implement interface 'com.mypackage.app.data.cache.query.Query'
in call to 'java.lang.String[] com.mypackage.app.data.cache.query.Query.c()'
(declaration of 'com.mypackage.app.data.cache.database.util.Db'
appears in /data/app/com.mypackage.app-2/base.apk:classes2.dex)
Two days of debugging later, we think the issue is related to Proguard stripping out the 'implements Query' during its shrinking phase. The interface itself is kept as it is used in hundreds of other classes, it is only missing in 3 classes. We also found some RxJava Func0 and Action0 interfaces stripped out in the same way, again in a very few places. Thus the app runs perfectly for 95% of the screens, but obviously crashes where the interface implementation is missing.
We are using Gradle build tools version 3.1.3 but also tried with 3.3.0-alpha03, also tried disabling D8, thus it shouldn't be a build tools issue (also Proguard version 5.3.3 and 6.0.3 behave in the same way in this case)
The issue appears when bumping Dagger2 from version 2.11 to 2.12 or later, thus it might be related to the amount of fields/methods/classes etc in the app
The issue remains even when using -dontoptimize, we have gone through Proguard documentation and enabled/disabled almost all relevant flags
The issue is gone by setting minifyEnabled to false or by using -dontshrink flag for Proguard
The app uses Multidex, both builds with Dagger 2.11 and Dagger 2.12 end up with 3 classes.dex files. The problematic interface and the implementations are in the same .dex file in both cases.
For example, there are 5 files in the same package that implement Query, 3 of them have the interface in the resulting bytecode, but 2 files do not. So it should not be related to where the files are placed.
Bytecode when built using Dagger 2.11 or -dontshrink
.class public Lcom/mypackage/app/data/cache/query/user/QueryUserFollowersCountById;
.super Ljava/lang/Object;
.source "SourceFile"
# interfaces
.implements Lcom/mypackage/app/data/cache/query/Query;
# annotations
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/lang/Object;",
"Lcom/mypackage/app/data/cache/query/Query<",
"Ljava/lang/Integer;",
">;"
}
.end annotation
......
Bytecode when built using Dagger 2.12 or later
.class public Lcom/mypackage/app/data/cache/query/user/QueryUserFollowersCountById;
.super Ljava/lang/Object;
.source "SourceFile"
# annotations
.annotation system Ldalvik/annotation/Signature;
value = {
"Ljava/lang/Object;"
}
.end annotation
......
We obviously want to use the latest version of Dagger2 and also keep shrinking and optimising our code with Proguard.
How to make sure that Proguard will not strip out implements interface statements?
Or how to add logging/debugging to Proguard shrinking step?
If proguard is removing implements part, it is more likely that during optimize phase proguard is considering it as dead code.
You have already mentioned
The issue is gone by setting minifyEnabled to false or by using -dontshrink flag for Proguard
This validates my assumption that implements part could be a dead code
please go through this thread, this explains how to get the list of dead code. In other words the answer to your query about adding logs for debugging using -printusage [filename]

With ProGuard, what is the impact on testing strategy?

I've needed to recently introduce ProGuard on Android because of issues with Scala on Android. I need ProGuard for its shrinking feature, which removes classes presumed to be unused. I'm very concerned about the impact of removing classes on testability.
As it stands, I write unit tests that run on the host and acceptance tests that run the fully integrated application on the Android platform.
Normally, I would be comfortable with relatively complete unit test coverage and spotty acceptance test coverage. However, given that in my code I use Guice dependency injection heavily, so far it's been my experience that ProGuard removes code in a manner that's difficult for me to predict. Because of this it's very likely to cause me to introduce bugs.
This leads me to believe that I need to write acceptance/platform tests that achieve full coverage because at any point, there may be a missing class.
Do others have this experience? If so, what has been your testing strategy? Or with experience, do you become more confident that the classes that ProGuard is removing are truly unneeded?
ProGuard will not break your application until it attempts to use reflection or Class#forName on removed classes and/or obfuscated members.
From my experience (with obfuscated Scala on Android too) it is really easy to spot problems caused by ProGuard to your Android application using the simple smoke tests. You know what libraries you include in your project. If some of them uses reflection or Class#forName - perform smoke test on them. Then exclude the necessary classes/members from the ProGuard configuration.
Remember also that you can automatize testing of your obfuscated project using the ActivityInstrumentationTestCase2 and emulator. If you plan to use ProGuard on your project, always perform instrumentation testing on obfuscated APK.
In conclusion - fear not. ProGuard-related problems are relativity easy to spot.
We've been both unit testing and "fully" testing our ProGuard-ed application for quite a while now, and we've had no "real" problems. The only issues we run into is when we use some library methods in our tests that aren't used in the main application; in these cases ProGuard will remove the code from the libraries and we would have to manually add the specific methods to proguard.cfg.
Oh, and we also use Guice :)

Categories

Resources