dagger
A fast dependency injector for Android and Java.
Dagger a fast dependency injector for android and java
How to use Dagger? How to configure Dagger to work in my Android project?
I'd like to use Dagger in my Android project, but I find it confusing.
EDIT: Dagger2 is also out since 2015 04 15, and it's even more confusing!
[This question is a "stub" on which I am adding to my answer as I learned more about Dagger1, and learn more about Dagger2. This question is more of a guide rather than a "question".]
Source: (StackOverflow)
Dagger 2 is around the corner but the available examples wouldn't even compile right off the box, and the documentation is a copy-paste-replace from Dagger 1.
Does anyone have an example of a proper application working on Google's Dagger 2?
Source: (StackOverflow)
Here is the issue,
I am working on a LibGDX project where i have different modules for different platforms.
This is how my android module looks like:
@Module(
includes = {BaseModule.class, NetModule.class},
injects = {DummyProjectActivity.class, DummyProject.class},
overrides = true)
public class DummyProjectAndroidModule {
...
@Provides @Singleton @Named("DummyOne")
DummyInterface provideDummyOne() {
return new DummyOne();
}
@Provides @Singleton @Named("DummyTwo")
DummyInterface provideDummyTwo() {
return new DummyTwo();
}
@Provides @Singleton @Named("DummyConsumer")
DummyConsumer provideDummyConsumer(@Named("DummyOne") DummyInterface dummyOne,
@Named("DummyTwo") DummyInterface dummyTwo) {
return new DummyConsumer(dummyOne, dummyTwo);
}
}
.. and here how my desktop module looks like:
@Module(
includes = {BaseModule.class, NetModule.class},
injects = {DummyProjectDesktop.class, DummyProject.class},
overrides = true)
public class DummyProjectDesktopModule {
well rest is pretty much the same. Yet while i'm building the project for Desktop everything goes nice and dandy where on Android side, i get this error which leaves me flabbergasted still.
Process: net.alicanhasirci.mobile.DummyProject.android, PID: 4603
java.lang.RuntimeException: Unable to start activity ComponentInfo{net.alicanhasirci.mobile.DummyProject.android/net.alicanhasirci.mobile.DummyProject.android.DummyProjectActivity}: java.lang.IllegalArgumentException: Duplicate:
net.alicanhasirci.mobile.android.image.DummyInterface net.alicanhasirci.mobile.DummyProject.android.DummyProjectAndroidModule.provideDummyOne()
net.alicanhasirci.mobile.android.image.DummyInterface net.alicanhasirci.mobile.DummyProject.android.DummyProjectAndroidModule.provideDummyTwo()
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2305)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2365)
at android.app.ActivityThread.access$800(ActivityThread.java:148)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1283)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5272)
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:909)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:704)
Caused by: java.lang.IllegalArgumentException: Duplicate:
net.alicanhasirci.mobile.android.image.DummyInterface net.alicanhasirci.mobile.DummyProject.android.DummyProjectAndroidModule.provideDummyOne()
net.alicanhasirci.mobile.android.image.DummyInterface net.alicanhasirci.mobile.DummyProject.android.DummyProjectAndroidModule.provideDummyTwo()
at dagger.internal.UniqueMap.put(UniqueMap.java:29)
at dagger.internal.plugins.reflect.ReflectiveModuleAdapter.handleBindings(ReflectiveModuleAdapter.java:104)
at dagger.internal.plugins.reflect.ReflectiveModuleAdapter.getBindings(ReflectiveModuleAdapter.java:89)
at dagger.ObjectGraph$DaggerObjectGraph.makeGraph(ObjectGraph.java:174)
at dagger.ObjectGraph$DaggerObjectGraph.access$000(ObjectGraph.java:132)
at dagger.ObjectGraph.create(ObjectGraph.java:129)
at net.alicanhasirci.mobile.DummyProject.android.DummyProjectActivity.onCreate(DummyProjectActivity.java:137)
at android.app.Activity.performCreate(Activity.java:5977)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2258)
now a quick glimpse at dagger source code and i can see that qualifier annotations are gathered by reflection and getting used as prefixes for binding names which will be later used as keys in UniqueMap. My problem seem to occur somewhere around this area where my qualifier does not get processed somehow, yet my desktop build works without a problem...
Here are some more additional:
ObjectGraph objectGraph = ObjectGraph.create(new DummyProjectAndroidModule());
objectGraph.inject(this);
dp = objectGraph.get(DummyProject.class);
is how i get my DummyProject
object, which has a field injection of DummyConsumer
. As such:
@Inject @Named("DummyConsumer") DummyConsumer consumer;
I have changed the return types to concrete classes as a workaround but nobody likes a workaround cause we all know that they haunt you till the end.
Source: (StackOverflow)
I started setting up dependency injection using Dagger as follows. Please feel encouraged to correct my implementation since I might have mistakes in there! The implementation follows the android-simple example provided by the project. In the following you can see how I successfully added dependency injection for Activities
and Fragments
. I try to keep it easy for now so I decided to inject Timber as a logger substitution for Android's log util.
import android.app.Application;
import java.util.Arrays;
import java.util.List;
import dagger.ObjectGraph;
import com.example.debugging.LoggingModule;
public class ExampleApplication extends Application {
private ObjectGraph mObjectGraph;
protected List<Object> getModules() {
return Arrays.asList(
new AndroidModule(this),
new ExampleModule(),
new LoggingModule()
);
}
private void createObjectGraphIfNeeded() {
if (mObjectGraph == null) {
Object[] modules = getModules().toArray();
mObjectGraph = ObjectGraph.create(modules);
}
}
public void inject(Object object) {
createObjectGraphIfNeeded();
mObjectGraph.inject(object);
}
}
By now the AndroidModule
is not used anywhere it but might be helpful when a Context
and LayoutInflater
is needed e.g. in CursorAdapters
.
import android.content.Context;
import android.view.LayoutInflater;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
/**
* A module for Android-specific dependencies which require a {@link Context}
* or {@link android.app.Application} to create.
*/
@Module(library = true)
public class AndroidModule {
private final ExampleApplication mApplication;
public AndroidModule(ExampleApplication application) {
mApplication = application;
}
/**
* Allow the application context to be injected but require that it be
* annotated with {@link ForApplication @Annotation} to explicitly
* differentiate it from an activity context.
*/
@Provides @Singleton @ForApplication Context provideApplicationContext() {
return mApplication;
}
@Provides @Singleton LayoutInflater provideLayoutInflater() {
return (LayoutInflater) mApplication
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
}
I am not sure what application-specific providers would go here. I stay with logging for now.
import dagger.Module;
@Module(
complete = false
)
public class ExampleModule {
public ExampleModule() {
// TODO put your application-specific providers here!
}
}
I prepared LoggingModule
which provides access to Timber.
package com.example.debugging;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import com.example.BuildConfig;
import com.example.activities.BaseFragmentActivity;
import com.example.activities.DetailsActivity;
import com.example.fragments.BaseListFragment;
import com.example.fragments.ProfilesListFragment;
import timber.log.Timber;
@Module(injects = {
// Activities
BaseFragmentActivity.class,
DetailsActivity.class,
// Fragments
BaseListFragment.class,
ProfilesListFragment.class
})
public class LoggingModule {
@Provides @Singleton Timber provideTimber() {
return BuildConfig.DEBUG ? Timber.DEBUG : Timber.PROD;
}
}
The base class for Activities
injects itself into the object graph ...
package com.example.activities;
import android.os.Bundle;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import javax.inject.Inject;
import com.example.ExampleApplication;
import timber.log.Timber;
public abstract class BaseFragmentActivity extends SherlockFragmentActivity {
@Inject Timber mTimber;
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
super.onCreate(savedInstanceState);
((ExampleApplication) getApplication()).inject(this);
}
}
... and any sub class benefits from Timber being already present.
package com.example.activities;
import android.os.Bundle;
import com.example.R;
public class DetailsActivity extends BaseFragmentActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_details);
mTimber.i("onCreate");
// ...
}
}
Same for Fragments
: the base class does the dirty job ...
package com.example.fragments;
import android.os.Bundle;
import com.actionbarsherlock.app.SherlockListFragment;
import javax.inject.Inject;
import com.example.ExampleApplication;
import timber.log.Timber;
public abstract class BaseListFragment extends SherlockListFragment {
@Inject Timber mTimber;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
((ExampleApplication) getActivity().getApplication()).inject(this);
}
}
... and the sub class benefits from its super class.
package com.example.fragments;
import android.os.Bundle;
public class ProfilesListFragment extends BaseListFragment {
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// TODO This might be a good example to inject resources
// in the base class. But how?
setEmptyText(getResources()
.getString(R.string.profiles_list_no_content));
mTimber.i("onActivityCreated");
// ...
}
}
So far so good. But how can inject Timber into BaseCursorAdapter
, BaseContentProvider
, BaseSQLiteOpenHelper
, BaseService
, BaseAsyncTask
and static
helper methods?
The deprecated android example by Christopher Perry points out how to inject an Adapter into a ListFragment but not how to inject Context
, Resources
, LayoutInflater
, Cursor
into the (Cursor)Adapter
or just Timber.
References:
Source: (StackOverflow)
I am evaluating Dependency Injection (DI) frameworks for an Android app. The top contenders are: Dagger (with Butter Knife) and Android Annotations. I understand that Dagger and ButterKnife are from the same source- square and they complement each other. Here're are the key matrices that I am looking for:
- Ease of use (our build is based on Gradle and we use Android Studio IDE)
- Testing support (we use Robotium for functional testing and RoboLectric for unit testing)
- Performance (DI frameworks use reflection, which one is faster?)
Source: (StackOverflow)
I have a simple Android activity with a single dependency. I inject the dependency into the activity's onCreate
like this:
Dagger_HelloComponent.builder()
.helloModule(new HelloModule(this))
.build()
.initialize(this);
In my ActivityUnitTestCase
I want to override the dependency with a Mockito mock. I assume I need to use a test-specific module which provides the mock, but I can't figure out how to add this module to the object graph.
In Dagger 1.x this is apparently done with something like this:
@Before
public void setUp() {
ObjectGraph.create(new TestModule()).inject(this);
}
What's the Dagger 2.0 equivalent of the above?
You can see my project and its unit test here on GitHub.
Source: (StackOverflow)
So I have come across this best practices on Android articles on memory performance.
http://developer.android.com/training/articles/memory.html
They said
Avoid dependency injection frameworks
Using a dependency injection framework such as Guice or RoboGuice may
be attractive because they can simplify the code you write and provide
an adaptive environment that's useful for testing and other
configuration changes. However, these frameworks tend to perform a lot
of process initialization by scanning your code for annotations, which
can require significant amounts of your code to be mapped into RAM
even though you don't need it. These mapped pages are allocated into
clean memory so Android can drop them, but that won't happen until the
pages have been left in memory for a long period of time.
Buy what about Dagger which they claim to be fast. Not sure which one should I go for?
Source: (StackOverflow)
Can anyone point out the difference between Dagger and Butterknife. I know that Butterknife is a view injection library and dagger is a dependency injection library. But the documentation online seems a bit overhead for me. According to Butterknife documentation you can do non-activity injections as well, which is what dagger does? Or did i misunderstood something?
If anyone can explain the differences between the two in simple terms it would be great.
Thanks!
http://square.github.io/dagger/
http://jakewharton.github.io/butterknife/
Source: (StackOverflow)
Jake Wharton's talk at Devoxx 2013, Architecting Android Applications with Dagger, talked about creating a Dagger scope for logged in users only. This sort of thing sounds really clean, and I want to do this in my applications.
The code that was discussed in the talk was something along the lines of:
public class LoggedInActivity extends Activity {
@Inject User user;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_logged_in);
DaggerScopesApp app = (DaggerScopesApp) getApplication();
app.getObjectGraph().plus(new UserModule("exampleusername")).inject(this);
findViewById(R.id.do_something_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(LoggedInActivity.this, user.username + " : " +
user.someValue++, Toast.LENGTH_SHORT).show();
}
});
}
}
However, if the injected User is scoped as a @Singleton, then it's properties will disappear on config change (as the object graph is created in onCreate).
The solution is pretty simple, you can just do this "plus" operation once and store the new object graph somewhere else (maybe the application class), but I was wondering if this is a good approach? Can anyone from Square can provide any insight into what you do in your applications? Do you just not have singleton objects in the "logged-in" graph?
Source: (StackOverflow)
I'm working on an Android
application and I've been using Dagger
for dependency injection.
I'm trying to now test a fragment which has one of these dependencies, let's call it ProductsService
.
In my Robolectric
test I have got as far as having a test module that overrides ProductsService
:
@Module(
includes = ProductsModule.class,
injects = {
Fragment.class,
FragmentTest.class
},
overrides = true
)
static class MockProductsModule {
@Provides
@Singleton
public ProductsService providesProductsService() {
return Mockito.mock(ProductsService.class);
}
}
In my test, in order to run my fragment I construct it as follows (as seen here How can I test fragments with Robolectric?)
FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class)
.create()
.start()
.resume()
.get();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
The problem is, during this creation it makes call to Dagger
to satisfy it's dependencies:
((MyApplication)getActivity().getApplication()).inject(this);
How do I override the object graph created when the fragment is created, to use the MockProductsModule
I declare in my test?
Source: (StackOverflow)
I've created an Android project using Gradle. I've added a dagger library
dependencies {
compile 'com.squareup.dagger:dagger-compiler:1.2.1'
compile 'com.squareup.dagger:dagger:1.2.1'
}
After trying to compile the project I get a build failure due to lint exceptions:
InvalidPackage: Package not included in Android
../../../../../../.gradle/caches/modules-2/files-2.1/com.squareup.dagger/dagger-compiler/1.2.1/c8bf6c2fda9b27f4d44a2aa4280df525a1408771/dagger-compiler-1.2.1.jar:
Invalid package reference in library; not included in Android:
javax.annotation.processing. Referenced from
dagger.internal.codegen.GraphAnalysisErrorHandler.
../../../../../../.gradle/caches/modules-2/files-2.1/com.squareup.dagger/dagger-compiler/1.2.1/c8bf6c2fda9b27f4d44a2aa4280df525a1408771/dagger-compiler-1.2.1.jar:
Invalid package reference in library; not included in Android:
javax.lang.model.type. Referenced from
dagger.internal.codegen.GeneratorKeys.
../../../../../../.gradle/caches/modules-2/files-2.1/com.squareup.dagger/dagger-compiler/1.2.1/c8bf6c2fda9b27f4d44a2aa4280df525a1408771/dagger-compiler-1.2.1.jar:
Invalid package reference in library; not included in Android:
javax.lang.model.util. Referenced from
dagger.internal.codegen.GraphAnalysisErrorHandler.
../../../../../../.gradle/caches/modules-2/files-2.1/com.squareup/javawriter/2.3.0/4b290e394ea3109be5c0b7f08ff75de089125122/javawriter-2.3.0.jar:
Invalid package reference in library; not included in Android:
javax.lang.model.element. Referenced from
com.squareup.javawriter.JavaWriter.
So, I have to either fix or make lint ignore dagger packages. I've setup lint.xml but I can't figure out a way to ignore:
<issue id="InvalidPackage">
<ignore ...INSERT DECLARATION TO IGNORE DAGGER PACKAGE... />
</issue>
Thanks.
Source: (StackOverflow)
I'm trying to use Dagger to inject into an android Annotated Activity.
java.lang.IllegalArgumentException: No inject registered for members/com.app.server.AddServerActivity_. You must explicitly add it to the 'injects' option in one of your modules.
If I try and Add the com.app.server.AddServerActivity_
to the module I get a diffrent error
Error: java.lang.ClassCastException: com.sun.tools.javac.code.Attribute$Error cannot be cast to com.sun.tools.javac.code.Attribute$Class
java.lang.RuntimeException: java.lang.ClassCastException: com.sun.tools.javac.code.Attribute$Error cannot be cast to com.sun.tools.javac.code.Attribute$Class
at com.sun.tools.javac.main.Main.compile(Main.java:469)
at com.sun.tools.javac.api.JavacTaskImpl.call(JavacTaskImpl.java:132)
at org.jetbrains.jps.javac.JavacMain.compile(JavacMain.java:167)
at org.jetbrains.jps.incremental.java.JavaBuilder.compileJava(JavaBuilder.java:364)
at org.jetbrains.jps.incremental.java.JavaBuilder.compile(JavaBuilder.java:276)
at org.jetbrains.jps.incremental.java.JavaBuilder.doBuild(JavaBuilder.java:190)
at org.jetbrains.jps.incremental.java.JavaBuilder.build(JavaBuilder.java:162)
at org.jetbrains.jps.incremental.IncProjectBuilder.runModuleLevelBuilders(IncProjectBuilder.java:1018)
at org.jetbrains.jps.incremental.IncProjectBuilder.runBuildersForChunk(IncProjectBuilder.java:742)
at org.jetbrains.jps.incremental.IncProjectBuilder.buildTargetsChunk(IncProjectBuilder.java:790)
at org.jetbrains.jps.incremental.IncProjectBuilder.buildChunkIfAffected(IncProjectBuilder.java:705)
at org.jetbrains.jps.incremental.IncProjectBuilder.buildChunks(IncProjectBuilder.java:526)
at org.jetbrains.jps.incremental.IncProjectBuilder.runBuild(IncProjectBuilder.java:314)
at org.jetbrains.jps.incremental.IncProjectBuilder.build(IncProjectBuilder.java:179)
at org.jetbrains.jps.cmdline.BuildRunner.runBuild(BuildRunner.java:129)
at org.jetbrains.jps.cmdline.BuildSession.runBuild(BuildSession.java:220)
at org.jetbrains.jps.cmdline.BuildSession.run(BuildSession.java:112)
at org.jetbrains.jps.cmdline.BuildMain$MyMessageHandler$1.run(BuildMain.java:132)
at org.jetbrains.jps.service.impl.SharedThreadPoolImpl$1.run(SharedThreadPoolImpl.java:41)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
at java.util.concurrent.FutureTask.run(FutureTask.java:166)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.ClassCastException: com.sun.tools.javac.code.Attribute$Error cannot be cast to com.sun.tools.javac.code.Attribute$Class
at com.sun.tools.javac.model.AnnotationProxyMaker$ValueVisitor.visitArray(AnnotationProxyMaker.java:190)
at com.sun.tools.javac.code.Attribute$Array.accept(Attribute.java:215)
at com.sun.tools.javac.model.AnnotationProxyMaker$ValueVisitor.getValue(AnnotationProxyMaker.java:165)
at com.sun.tools.javac.model.AnnotationProxyMaker.generateValue(AnnotationProxyMaker.java:143)
at com.sun.tools.javac.model.AnnotationProxyMaker.getAllReflectedValues(AnnotationProxyMaker.java:101)
at com.sun.tools.javac.model.AnnotationProxyMaker.generateAnnotation(AnnotationProxyMaker.java:86)
at com.sun.tools.javac.model.AnnotationProxyMaker.generateAnnotation(AnnotationProxyMaker.java:78)
at com.sun.tools.javac.model.JavacElements.getAnnotation(JavacElements.java:108)
at com.sun.tools.javac.model.JavacElements.getAnnotation(JavacElements.java:121)
at com.sun.tools.javac.code.Symbol$ClassSymbol.getAnnotation(Symbol.java:888)
at dagger.internal.codegen.ValidationProcessor.validateProvides(ValidationProcessor.java:75)
at dagger.internal.codegen.ValidationProcessor.process(ValidationProcessor.java:67)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.callProcessor(JavacProcessingEnvironment.java:793)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.discoverAndRunProcs(JavacProcessingEnvironment.java:722)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.access$1700(JavacProcessingEnvironment.java:97)
at com.sun.tools.javac.processing.JavacProcessingEnvironment$Round.run(JavacProcessingEnvironment.java:1029)
at com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing(JavacProcessingEnvironment.java:1163)
at com.sun.tools.javac.main.JavaCompiler.processAnnotations(JavaCompiler.java:1108)
at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:824)
at com.sun.tools.javac.main.Main.compile(Main.java:439)
... 24 more
Edit: ok, it seems to be a known issue with how dagger deals with classes generated by other processors.
https://github.com/square/dagger/issues/322
Source: (StackOverflow)
In this question I talk about Dagger2. Dagger2 consists basically of Components and Modules. Here is an example:
Assume I have a interface:
public interface MyCoolService {
void run();
}
and a possible implementation:
public class MyCoolServiceImpl {
@Override
void run() {}
}
I could link the implementation with the interface using Dagger2 generating:
@Component(modules = {MyModule.class})
@Singleton
public interface Component {
MyCoolService getMyCoolService();
}
and
@Module
public class MyModule {
@Provides @Singleton
MyCoolService provideMyCoolService() {
return new MyCoolServiceImpl();
}
}
This was a brief intro to Dagger2. Now assume I have the following interface:
public interface MySecondCoolService {
void doCoolStuff();
}
There is no implementation MySecondCoolServiceImpl
of MySecondCoolService
in code. Instead, I have an Annotations @JustForCoolStuff
to mark fields and methods. I created an Annotation processor which collects all these Annotations and generates MySecondCoolServiceImpl
which implements MySecondCoolService
.
I the compiler knows the new interface MySecondCoolService
before the annotation processor is running. So I could change my Component as:
@Component(modules = {MyModule.class})
@Singleton
public interface Component {
MyCoolService getMyCoolService();
MySecondCoolService getMySecondCoolService();
}
The problem is that I do not have an implementation yet in code and I do not know the name of the implementation of MySecondCoolService
which will be generated by a annotation processor. Therefore, I cannot wire the interface with the correct implemantation in MyModule
. What I can do is changing my annotation processor such that it generates a new module for me. My annotation processor could generate a module (MyGeneratedModule
) like this:
@Module
public class MyGeneratedModule {
@Provides @Singleton
MySecondCoolService provide MySecondCoolService() {
return new MySecondCoolServiceImpl();
}
}
Again MyGeneratedModule
is generated by an annotation processor. I do not have access to it before running the annotation processor also I do not know the name.
Here is the problem: The annotation processor somehow have to tell Dagger2 that there is a new module which Dagger2 should take into account. Since annotation processors cannot change files it cannot extend the @Component(modules = {MyModule.class})
annotation and change it into something like this: @Component(modules = {MyModule.class, MyGeneratedModule.class})
Is there a way to add MyGeneratedModule
programmatically to the dagger2 dependency graph? How can my Annotation Processor tell Dagger2 that there should be a new wiring between an interface and an implementation as I have described above?
Foray:
I know that something like that can be done in Google Guice and Google Gin. A project which does that is GWTP. There you have a Presenter:
public class StartPagePresenter extends ... {
@NameToken("start")
public interface MyProxy extends ProxyPlace<StartPagePresenter> {
}
...
}
which has a @NameToken
annotation to a ProxyPlace
interface. In your AbstractPresenterModule
you wire the view with the presenter and the proxy:
public class ApplicationModule extends AbstractPresenterModule {
bindPresenter(StartPagePresenter.class,
StartPagePresenter.MyView.class, StartPageView.class,
StartPagePresenter.MyProxy.class);
...
}
As so can see no implementation of the MyProxy
interface is given. The implementation created by a Generator (similar to annotation processor but for GWT). There Generator generates the implementation of StartPagePresenter.MyProxy
and add it to the guide/gin system:
public class StartPagePresenterMyProxyImpl extends com.gwtplatform.mvp.client.proxy.ProxyPlaceImpl<StartPagePresenter> implements buddyis.mobile.client.app.start.StartPagePresenter.MyProxy, com.gwtplatform.mvp.client.DelayedBind {
private com.gwtplatform.mvp.client.ClientGinjector ginjector;
@Override
public void delayedBind(Ginjector baseGinjector) {
ginjector = (com.gwtplatform.mvp.client.ClientGinjector)baseGinjector;
bind(ginjector.getPlaceManager(),
ginjector.getEventBus());
presenter = new CodeSplitProvider<StartPagePresenter>( ginjector.getbuddyismobileclientappstartStartPagePresenter() );
...
}
}
Source: (StackOverflow)
I've recently gone whole-hog with Dagger because the concept of DI makes complete sense. One of the nicer "by-products" of DI (as Jake Wharton put in one of his presentations) is easier testability.
So now i'm basically using espresso to do some functional testing, and i want to be able to inject dummy/mock data to the application and have the activity show them up. I'm guessing since, this is one of the biggest advantages of DI, this should be a relatively simple ask. For some reason though, I can't seem to wrap my head around it. Any help would be much appreciated. Here's what i have so far (I've written up an example that reflects my current setup):
public class MyActivity
extends MyBaseActivity {
@Inject Navigator _navigator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.get(this).inject(this);
// ...
setupViews();
}
private void setupViews() {
myTextView.setText(getMyLabel());
}
public String getMyLabel() {
return _navigator.getSpecialText(); // "Special Text"
}
}
These are my dagger modules:
// Navigation Module
@Module(library = true)
public class NavigationModule {
private Navigator _nav;
@Provides
@Singleton
Navigator provideANavigator() {
if (_nav == null) {
_nav = new Navigator();
}
return _nav;
}
}
// App level module
@Module(
includes = { SessionModule.class, NavigationModule.class },
injects = { MyApplication.class,
MyActivity.class,
// ...
})
public class App {
private final Context _appContext;
AppModule(Context appContext) {
_appContext = appContext;
}
// ...
}
In my Espresso Test, I'm trying to insert a mock module like so:
public class MyActivityTest
extends ActivityInstrumentationTestCase2<MyActivity> {
public MyActivityTest() {
super(MyActivity.class);
}
@Override
public void setUp() throws Exception {
super.setUp();
ObjectGraph og = ((MyApplication) getActivity().getApplication()).getObjectGraph().plus(new TestNavigationModule());
og.inject(getActivity());
}
public void test_SeeSpecialText() {
onView(withId(R.id.my_text_view)).check(matches(withText(
"Special Dummy Text)));
}
@Module(includes = NavigationModule.class,
injects = { MyActivityTest.class, MyActivity.class },
overrides = true,
library = true)
static class TestNavigationModule {
@Provides
@Singleton
Navigator provideANavigator() {
return new DummyNavigator(); // that returns "Special Dummy Text"
}
}
}
This is not working at all. My espresso tests run, but the TestNavigationModule is completely ignored... arr... :(
What am i doing wrong? Is there a better approach to mocking modules out with Espresso. I've searched and seen examples of Robolectric, Mockito etc. being used. But I just want pure Espresso tests and need to swap out a module with my mock one. How should i be doing this?
EDIT:
So i went with @user3399328 approach of having a static test module list definition, checking for null and then adding it in my Application class. I'm still not getting my Test injected version of the class though. I have a feeling though, its probably something wrong with dagger test module definition, and not my espresso lifecycle. The reason i'm making the assumption is that i add debug statements and find that the static test module is non-empty at time of injection in the application class. Could you point me to a direction of what i could possibly be doing wrong. Here are code snippets of my definitions:
MyApplication:
@Override
public void onCreate() {
// ...
mObjectGraph = ObjectGraph.create(Modules.list(this));
// ...
}
Modules:
public class Modules {
public static List<Object> _testModules = null;
public static Object[] list(MyApplication app) {
// return new Object[]{ new AppModule(app) };
List<Object> modules = new ArrayList<Object>();
modules.add(new AppModule(app));
if (_testModules == null) {
Log.d("No test modules");
} else {
Log.d("Test modules found");
}
if (_testModules != null) {
modules.addAll(_testModules);
}
return modules.toArray();
}
}
Modified test module within my test class:
@Module(overrides = true, library = true)
public static class TestNavigationModule {
@Provides
@Singleton
Navigator provideANavigator()() {
Navigator navigator = new Navigator();
navigator.setSpecialText("Dummy Text");
return navigator;
}
}
Source: (StackOverflow)
I am trying to get Dagger up an working on my project.
However I get the following exception on one of my classes during compilation:
error: No injectable members on Foo. Do you want to add an injectable constructor?
However, the class have no dependencies and as such uses the default no-arg constructor:
public class Foo
{
...
}
Do I really have to add an injectable no-arg constructor like below?
public class Foo
{
@Inject
public Foo()
{
}
....
}
Source: (StackOverflow)