I have been testing out Dagger 2, and everything had been working, until I did a bit of refactoring. Now gradle is throwing an IllegalArgumentException, and I cannot figure out what I changed that is now causing the error. I haven't made any changes to the gradle file, and this seems to be the brunt of the stack trace:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':mobile:compileDebugJavaWithJavac'.
at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:69)
...
Caused by: java.lang.IllegalArgumentException
at com.google.common.base.Preconditions.checkArgument(Preconditions.java:108)
at dagger.internal.codegen.writer.ClassName.peerNamed(ClassName.java:130)
at dagger.internal.codegen.SourceFiles.membersInjectorNameForMembersInjectionBinding(SourceFiles.java:266)
at dagger.internal.codegen.InjectBindingRegistry.registerBinding(InjectBindingRegistry.java:194)
at dagger.internal.codegen.InjectBindingRegistry.registerBinding(InjectBindingRegistry.java:171)
at dagger.internal.codegen.InjectProcessingStep.process(InjectProcessingStep.java:129)
at dagger.shaded.auto.common.BasicAnnotationProcessor.process(BasicAnnotationProcessor.java:228)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:794)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:705)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1800(JavacProcessingEnvironment.java:91)
at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1035)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1176)
at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1170)
at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:856)
at com.sun.tools.javac.main.Main.compile(Main.java:523)
... 89 more
No files are being generated by Dagger as well, and they were previously. I have been trying every method to fix this that I can find, mostly involving fixing the gradle files or clearing out the build folder, but so far nothing has worked.
Quick update (since I noticed a few up-votes); I never did find out what I did wrong, I ended up reverting to an old build. After the revert, I did the refactoring again and it worked fine. I must've done something different when I initially refactored the code, but I have no idea what it was.
If anyone has an idea of what could have caused this, I'm sure it will help out anyone else who has, or will in the future, run into this issue.
I ran into this issue while bringing Firebase into the project. It was the first background service being added to the project so I decided to do some sleuthing with a service that did nothing.
This built:
public class HopefullyBuildsService extends IntentService {
public HopefullyBuildsService(String name) {
super(name);
}
#Override
protected void onHandleIntent(Intent intent) {
}
}
..............
#ApplicationScoped
#Component(modules = {ApplicationModule.class, RestModule.class})
public interface ApplicationComponent {
...
void inject(HopefullyBuildsService service);
...
}
But this caused the build to fail:
public class HopefullyBuildsService extends FirebaseMessagingService {
}
..............
#ApplicationScoped
#Component(modules = {ApplicationModule.class, RestModule.class})
public interface ApplicationComponent {
...
void inject(HopefullyBuildsService service);
...
}
For whatever reason trying to inject directly into a Firebase derived service causes the build to fail in the way you described. However indirectly injecting into another class and then instantiating it the old-fashioned way inside the service allowed it to build again.
public class FirebaseDaggerInjectHelper {
#Inject
PersistManager persistManager;
#Inject
RestClient restClient;
#Inject
OtherClasses stuffEtc;
public FirebaseDaggerInjectHelper(MyApplication application){
application.getApplicationComponent().inject(this);
}
//getters and setters n stuff
}
........
#ApplicationScoped
#Component(modules = {ApplicationModule.class, RestModule.class})
public interface ApplicationComponent {
...
void inject(FirebaseDaggerInjectHelper helper);
...
}
........
public class HopefullyBuildsService extends FirebaseMessagingService {
private FirebaseDaggerInjectHelper injectHelper;
#Override
public void onCreate() {
super.onCreate();
injectHelper = new FirebaseDaggerInjectHelper((MyApplication) getApplicationContext());
}
And then it built fine. Admittedly, having this middleman class is annoying and the firebase derived service has to interact with the injected components in an indirect fashion. But its not clear to me why I cannot inject into a Firebase derived service, Or what is special about Firebase that made Dagger2 unhappy.
This is not yet solved in dependency dagger2.0 still throws IllegalArgumentException, I agree with #KATHYxx's approach to solve it in dagger2.0
But square has solved the inject in dagger2.7 version.
So, updating the dependency fixed the issue
implementation "com.google.dagger:dagger:2.7"
apt "com.google.dagger:dagger-compiler:2.7"
Related
I am using Hilt for dependency injection, and I wanted to start a singleton Android Service (DeviceConnectionService), and be able to access that Service object to do something to it
I observed 2 instances of DeviceConnectionService being created even though it was denoted as Singleton. Any idea or advice on this? Thanks in advance!
Following is my code setup:
Android Library: DeviceLibrary
Android Service
#AndroidEntryPoint
#Singleton
public class DeviceConnectionService extends Service {
#Inject
public DeviceConnectionService () {
Timber.d("DEVICE connection : " + hashCode());
}
}
Another classes that wants to be injected with the Android Service - to do something:
#Singleton
public class Connection implements IHololensConnection {
#Inject
DeviceConnectionService connectionService;
...
}
App
MainActivity.java
#AndroidEntryPoint
public class MainActivity extends AppCompatActivity {
...
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
...
Intent intent = new Intent(getApplicationContext(), DeviceConnectionService.class);
startService(intent);
}
...
}
Dagger doesn't start services, it just injects them once Android starts them. If you're observing that your Service is instantiated multiple times, that's unrelated to Hilt/Dagger/#Singleton: You'll need to ensure that your Service doesn't stop itself, and that nothing else stops it.
(This would be slightly different if your Service were instantiated exactly once, and objects that your Service injects were instantiated more times than you wanted. In that case you would need to put the #Singleton annotation on your binding for that class in a Module. However, that only applies to objects that Dagger creates, which excludes Service, Activity, or anything else marked with #AndroidEntryPoint.)
We used RoboGuice, but it's deprecated I start replace it with Dagger2.
// https://github.com/google/dagger
compile('com.google.dagger:dagger:2.7')
annotationProcessor 'com.google.dagger:dagger-compiler:2.7'
provided 'org.glassfish:javax.annotation:10.0-b28'
#Module
public class ApplicationModule {
Application mApp;
public ApplicationModule(#NonNull Application app) {
Preconditions.checkNotNull(app);
mApp = app;
}
#Provides
#Singleton
public SharedPreferences providesSharedPrefs() {
return PreferenceManager.getDefaultSharedPreferences(mApp);
}
#Provides
#Singleton
public DateHelper providesDateHelper() {
return new DateHelper(mApp);
}
#Provides
#Singleton
public PersistentConfig providesPersistentConfig() {
return new PersistentConfig(mApp);
}
#Provides
#Singleton
public OttoBus providesOttoBus() {
return new OttoBus();
}
}
public class Application extends MultiDexApplication {
private ApplicationComponent mApplicationComponent;
#Override
public void onCreate() {
super.onCreate();
mApplicationComponent = DaggerApplicationComponent.builder()
.applicationModule(new ApplicationModule(this))
.build();
mApplicationComponent.inject(this);
}
public static Application getApp(#NonNull Context context) {
return (Application) context.getApplicationContext();
}
public static ApplicationComponent getApplicationComponent(#NonNull Context context) {
return getApp(context).getApplicationComponent();
}
}
And after everywhere when I want to inject ApplicationComponent
For example MainActivity
public class MainActivity extends AppCompatActivity {
#Inject
PersistentConfig mPersistentConfig;
#Inject
OttoBus mOttoBus;
#Override
public void onCreate(Bundle savedInstanceState) {
Helper.manageRotation(this);
super.onCreate(null);
setContentView(R.layout.main_layout);
Application.getApplicationComponent(this).inject(this);
}
}
Application.getApplicationComponent(context).inject(this);
First question: I'm really confused about interface ApplicationComponent which must provide all activities/fragments/services (etc) where I want to use injection. But I can't use generic objects like Activity / Fragment. Or am I really out of reality and don't understand how Dagger2 works?
Because this is really crazy for project with about 50+ activities and a tons of fragments/services...
#Singleton
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
void inject(#NonNull Application app);
void inject(#NonNull MainActivity object);
void inject(#NonNull DispatcherActivity object);
void inject(#NonNull DateTimeHelper object);
void inject(#NonNull DatabaseHelper object);
void inject(#NonNull LandingPageActivityFragment object);
void inject(#NonNull RedirectActivity object);
void inject(#NonNull CategoryFragment object);
void inject(#NonNull BaseModuleFragment object);
void inject(#NonNull NotificationHelper object);
void inject(#NonNull RecordAdapter object);
void inject(#NonNull PagingProvider object);
void inject(#NonNull FilterDialog object);
... next 100+ injections?
}
Said me, that it can't be real...
Second question: How I can provide to inject generic classes, when I can't use it like void inject(#NonNull NotificationHelper<? extends GenericObject> object); because it require specific object. So I must write all this objects inside ApplicationComponent and not use ? notation?
It's a much more than just crazy :(. Maybe better stay with RoboGuice which is much more developer friendly and don't need make this overhead and manual check every injected objects? When I forgot add them to this list, I will get NPE in runtime (when I will not test it a lot it will crash customers).
It's much faster write it manually, than make a list of all object when it's not possible to use generic objects like Activity / Fragment / Service.
Is there a better solution, when I don't want use same generic BaseActivity which will inject every part of ApplicationModule and every activity will be extended by this huge BaseActivity?
This question has aspects of a complaint, but to attempt an answer:
I'm really confused about interface ApplicationComponent which must provide all activities/fragments/services (etc) where I want to use injection. But I can't use generic objects like Activity / Fragment. Or am I really out of reality and don't understand how Dagger2 works?
This is, indeed, how Dagger 2 works; it you must statically supply the type of the injection target inside the injector (component) and you cannot use 'generic' (covariant) types. Dagger 2 does this in order to maintain a DI framework that is 100% static.
Note that you are specifying RecordAdapter and DatabaseHelper as injection sites. You probably don't need to do that, you should try and only specify top level objects for which the constructor is not visible (Activity, Fragment, and Service) as injection sites. The rest of the objects should be able to be constructed through annotating their dependencies with #Inject and specifying their dependencies, if any, in a Module.
Maybe better stay with RoboGuice which is much more developer friendly and don't need make this overhead and manual check every injected objects
Yes Roboguice is more friendly in the sense that you don't have to worry about specifying the injection targets. However, consider the following in Roboguice: 1. The 'red stacktrace of death' you get when you set up your object graph incorrectly
2. The fact that you cannot get see which implementations of interfaces are actually being used in your project with Find Usages which can also be 'developer unfriendly'
Is there a better solution, when I don't want use same generic BaseActivity which will inject every part of ApplicationModule and every activity will be extended by this huge BaseActivity?
Well, it would depend which dependencies you are using and where. If you have a small list of dependencies that you want to inject everywhere, that may be the best solution i.e., make a BaseActivity that receives injection of these and makes this available to all of your subclasses. Alternatively, you can use sub-components and modules you can divide up your object graph so that you can group consumers/injection targets together with the correct modules. Then you don't need to have one 'god' component that lists all of the injection sites.
Second question: How I can provide to inject generic classes, when I can't use it like void inject(#NonNull NotificationHelper object); because it require specific object. So I must write all this objects inside ApplicationComponent and not use ? notation?
Yes, you must supply the invariant type of the injection target. I am not sure if your NotificationHelper<String> is a top level type. Why not inject it through the object graph when you inject in a Fragment, Activity or Service?
If it absolutely must be an injection target you will need to subclass: NotificationHelper<String> and Notification<Integer> become StringNotificationHelper extends NotificationHelper<String>, IntegerNotficationHelper extends NotificationHelper<Integer>. This is a practice recommended in the book Clean Code.
You don't need to write it all the injection sites inside the ApplicationComponent, you may create subcomponents that correspond with the consumption patterns of the dependencies in your project.
(disclosure: as someone who is currently trying to migrate a project from Roboguice to Dagger 2 I am sympathetic to your complaint)
Thanks, we solved it as you described a week ago. Using every objects as injected.
Better solution for it is don't use only inject but complex name. Why? Because it will help to resolve why some object is not injected (you know, base classes and so on).
#Singleton
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
void injectApplication(#NonNull Application app);
void injectMainActivity(#NonNull MainActivity object);
void injectDispatcherActivity(#NonNull DispatcherActivity object);
...
}
We finally use for genericity UtilityWrapper as is described here: https://medium.com/#patrykpoborca/dagger-2-and-base-classes-generics-and-presenter-injection-7d82053080c#.b58ykd4cm
So I am working on this little project that uses Dagger 2 for dependency injection and Realm as a database.
I am unit testing it with Robolectric and Mockito (with Powermock). From previous research (and a lot of pain) I realised testing Realm is pretty laborious, but has been done in the past here.
Now, my project has a very similar setup and structure to the one linked above.
When I run my unit tests, all of them pass except for one that gives me a very cryptic message that looks as follows:
java.lang.NullPointerException
at org.robolectric.internal.ShadowExtractor.extract(ShadowExtractor.java:5)
at org.robolectric.Shadows.shadowOf(Shadows.java:1190)
at org.robolectric.shadows.CoreShadowsAdapter.getMainLooper(CoreShadowsAdapter.java:37)
at org.robolectric.util.ComponentController.<init>(ComponentController.java:31)
at org.robolectric.util.ComponentController.<init>(ComponentController.java:23)
at org.robolectric.util.ActivityController.<init>(ActivityController.java:40)
at org.robolectric.util.ActivityController.of(ActivityController.java:32)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:82)
at org.robolectric.Robolectric.buildActivity(Robolectric.java:78)
at org.robolectric.Robolectric.setupActivity(Robolectric.java:86)
at uk.co.placona.tradesafe.view.EditActivityTest.ActivityShouldNotBeNull(EditActivityTest.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.powermock.modules.junit4.internal.impl
The line of code specified on the error above is:
activity = Robolectric.setupActivity(EditActivity.class);
The activity exists, and has a TradeRepository injected to it when it starts up.
The activity in question can be found here along with the rest of the code. I have been trying to debug this for about 3 days now with no success. Every other unit test I create works fine, except any unit test that is used by an Activity, which makes me think I'm probably missing something really obvious.
Would be happy to clarify any questions here. With many thanks!
static is evil, powermock is evil :).
I think you should get rid of your class Injector. You don't need it because you have only one CustomApplication object during the lifetime of your application.
You should modify your code as follow:
In CustomApplication.java, the application component is created, set in a field variable, and injected
private ApplicationComponent applicationComponent;
public void setup(){
getOrCreateApplicationComponent().inject(this);
databaseRealm.setup();
stethoDebug.setup(this);
}
public ApplicationComponent getOrCreateApplicationComponent() {
if (applicationComponent == null) {
applicationComponent = DaggerApplicationComponent.builder()
.applicationContextModule(new ApplicationContextModule(this))
.repositoryModule(new RepositoryModule())
.build();
}
return applicationComponent;
}
In the methods onCreate of CreateActivity, EditActivity, and MainActivity, Injector is replaced with
((CustomApplication) getApplication())
.getOrCreateApplicationComponent()
.inject(this);
In RepositoryModule we will use Dagger 2 to inject dependencies into constructors so we don't need to inject manually the Context and the DatabaseRealm
#Provides
#Singleton
public TradeRepository provideTradeRepository(DatabaseRealm databaseRealm) {
return new TradeRepositoryImpl(databaseRealm);
}
#Provides
#Singleton
public DatabaseRealm provideDatabaseRealm(Context context) {
return new DatabaseRealm(context);
}
then in DatabaseRealm we add a constructor with Context as parameter
Context mContext;
RealmConfiguration realmConfiguration;
public DatabaseRealm(Context context) {
mContext = context;
}
and same in TradeRepositoryImpl, a constructor with databaseRealm is added
DatabaseRealm databaseRealm;
public TradeRepositoryImpl(DatabaseRealm databaseRealm) {
this.databaseRealm = databaseRealm;
}
For RepositoryTestModule, we add databaseRealm as parameter:
#Provides
#Singleton
public TradeRepository provideTradeRepository(DatabaseRealm databaseRealm) {
return isMocked ? mock(TradeRepository.class) : new TradeRepositoryImpl(databaseRealm);
}
in your TestCustomApplication we override the getOrCreateApplicationComponent
#Override
public ApplicationComponent getOrCreateApplicationComponent() {
return DaggerApplicationComponentTest.builder()
.applicationContextModuleTest(new ApplicationContextModuleTest())
.repositoryModuleTest(new RepositoryModuleTest(false))
.build();
}
Now for each of your tests we run them with RobolectricGradleTestRunner and add TestCustomApplication.class as application tag
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21, application = TestCustomApplication.class)
when we need to inject dependencies into ours tests we will inject like this:
#Before
public void setupDagger() {
DaggerApplicationComponentTest.builder()
.applicationContextModuleTest(new ApplicationContextModuleTest())
.repositoryModuleTest(new RepositoryModuleTest(false))
.build().inject(this);
}
We still have a NullPointerException in our EditActivityTest because this line:
loadTrade(intent.getExtras().getString("ID"));
Either you check the intent is not null or you provide one in your test.
Looking to your test class, I don't see that you use any of Robolectric test runners.
You must use RobolectricGradleTestRunner or RobolectricTestRunner to trigger Robolecric functionality about loading manifest, parsing resources, creating main looper, etc.
If you don't use them, you probably can achieve it with own code in setup, but it is not the usual way, and I'm not sure that many people here can explain to you how to achieve it.
As well, Robolectric and PowerMock are modifying java ClassLoader both. That is why it is so hard (maybe impossible) to get them together working. So check #Steve answer how to modify your code to remove PowerMock necessity for your test.
TL;DR;
Is it acceptable for a class to depend on the ObjectGraph itself?
I need this because I need to inject dependencies on some objects that I load at runtime - at a time that is disconnected from the point at which the ObjectGraph is initialized. Here is an example that illustrates how I use ServiceLoader framework to load concrete implementation classes of a Service at runtime, and then inject dependencies into the loaded implementation classes.
interface Plugin {
void doSomething();
}
class AwesomePlugin implements plugin {
#Inject DependencyOne dependencyOne;
#Inject DependencyTwo dependencyTwo;
void doSomething(){
// ...some implementation...
}
}
class PluginEngine{
public void start(){
ServiceLoader<Plugin> pluginLoader = ServiceLoader.load(Plugin.class);
for(Plugin plugin: pluginLoader){
//TODO: Inject plugin dependencies here
}
}
}
Doing this would require the PluginEngine class to have access to the ObjectGraph instance:
class PluginEngine{
private final ObjectGraph objectGraph;
public PluginEngine(ObjectGraph graph){
this.objectGraph = graph;
}
public void start(){
ServiceLoader<Plugin> pluginLoader = ServiceLoader.load(Plugin.class);
for(Plugin plugin: pluginLoader){
objectGraph.inject(plugin);
}
}
}
Is this a code smell? Is this pointing to some problem elsewhere in my code, or in the way my dependencies are set up?
While composing this question, I began to see the role of Dagger as a means of replacing arbitrary dependencies with a dependency on the ObjectGraph itself. On Android, you use a reference to the custom Application sub-class and use it to perform injection - which is basically just a means to get access to the ObjectGraph itself. Is this reasoning flawed?
To answer my own question, it looks like this is acceptable. The u2020 sample app does something roughly similar. In fact it makes some very clever use of getSystemService() to achieve this but that is Android specific. In particular, look at Injector.java and how it is used from within custom views like TrendingView
So, conceptually, one could do something like this - which basically abstracts the concrete ObjectGraph dependency behind an Injector interface.
class PluginEngine{
private final Injector injector;
public PluginEngine(Injector injector){
this.injector = injector;
}
public void start(){
ServiceLoader<Plugin> pluginLoader = ServiceLoader.load(Plugin.class);
for(Plugin plugin: pluginLoader){
injector.inject(plugin);
}
}
}
This can be refined/adjusted in various ways such that the injector dependency is provided via a constructor or obtained in other ways.
I have an Android Studio android project with two modules, Module A and Module B. I am building and testing these modules and then distributing them as .aar files. When the parent app that uses the .aars runs I am encountering an AbstractMethodError and I can't figure out why. I have included -keep flags for classes and interfaces in the dexguard-project.txt files of both modules in the hopes that it would work but to no avail. Here's more information about the project:
Module A contains a class called Util.class.
public class Util {
private static CustomObject getObjectFromDb(Context context) {
return new CustomObject();
}
public static class GetObjectTask extends AsyncTask<Context, Void, CustomObject> {
FetchCustomObjectListener mListener;
Context mContext;
public GetObjectTask(Context context, FetchCustomObjectListener listener) {
mListener = listener;
mContext = context;
}
#Override
protected CustomObject doInBackground(Context... params) {
return getObjectFromDb(mContext);
}
#Override
protected void onPostExecute(CustomObject d) {
super.onPostExecute(d);
mListener.onCustomObjectFetched(d);
}
}
}
Module A also contains an interface called FetchCustomObjectListener.class
public interface FetchCustomObjectListener {
public void onObjectFetched(CustomObject d);
}
Module B contains a class called Startup.class:
public class Startup {
private Startup(Context context) {
super(context);
Util.GetObjectTask getObjectTask = new Util.GetObjectTask(context, new FetchCustomObjectListener() {
#Override
public void onObjectFetched(CustomObject d) {
//handle custom object here
}
});
getObjectTask.execute();
}
At runtime the Startup class creates an instance of GetObjectTask and executes it. GetObjectTask grabs an object from the database and tries to return it to Startup class via the interface FetchObjectListener. At this point I am getting the following error:
java.lang.AbstractMethodError: abstract method "void a.b.c.FetchObjectListener.onObjectFetched(a.b.c.CustomObject)"
at a.b.c.Util$GetObjectTask.onPostExecute(SourceFile:65)
at a.b.c.Util$GetObjectTask.onPostExecute(SourceFile:48)
at android.os.AsyncTask.finish(AsyncTask.java:632)
at android.os.AsyncTask.access$600(AsyncTask.java:177)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:645)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5221)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
As I understand it, this error can occur when an interface is 'kept' by one module or class and not 'kept' by another when Dexguard is run. So one module has the actual name and one has the obfuscated name and because of this the two modules can't communicate using the interface and so the AbstractMethodError is thrown.
In the past I have used Dexguard to successfully compile and run this project, but have since modularized the project more and feel that this may be part of the problem. I'm trying to narrow down what could possibly be a problem and thought that perhaps two modules trying to use an interface might be causing the problem.
Any ideas on how to solve this would be appreciated.
It turns out to be an incorrect file filter on the first library when processing the second library. You may have seen the warnings about missing classes from the first library. The problem has been fixed in DexGuard 6.1.15.
Note that processing the final application (including its libraries) is more effective than processing the individual libraries, if you have the choice.