Showing posts with label dagger-2. Show all posts
Showing posts with label dagger-2. Show all posts

Monday, December 4, 2017

Dagger singleton creating new instance every time

Leave a Comment

I have a module as follows.

@Module public class AppModule {     private final Application app;      public AppModule(Application app) {         this.app = app;     }      @Provides     @Architecture.ApplicationContext     Context provideContext() {         return app;     }      @Provides //scope is not necessary for parameters stored within the module     public Context context() {         return provideContext();     }      @Singleton     @Provides     Application provideApp() {         return app;     }      @Singleton     @Provides     SoundsRepository provideSoundsRepository(Context context, SoundsDAO soundsDAO) {         return new SoundsRepository(context, soundsDAO);     } } 

A component like this.

@Singleton @Component(modules = AppModule.class) public interface AppComponent {      void inject(Global global);      void inject(MainActivity mainActivity);       @Architecture.ApplicationContext     Context getContext();      Application getApplication();      void inject(PostView postView);      void inject(MediaPlayerService mediaPlayerService); } 

In activity, fragment or service, I do this

@Inject     SoundsRepository soundsRepository;  @Override protected void onCreate(...) {     //....     ((Global) getApplication()).getComponent().inject(this); } 

In SoundsRepository

@Singleton public class SoundsRepository {     @Inject     public SoundsRepository(Context context, SoundsDAO soundsDAO) {         this.context = context;         this.soundsDAO = soundsDAO;         System.out.println(TAG + "INIT");     }     // ....  } 

So, now, every time I start to access an activity or service where SoundsRepository is injected, I get a new instance, I mean, the constructor of "SoundsRepository" fires again.

What am I doing wrong?

EDIT : Inject in Application Class

public class Global extends MultiDexApplication {      protected AppComponent appComponent;     private boolean calledAlready = false;      @Override     public void onCreate() {         super.onCreate();         //if (LeakCanary.isInAnalyzerProcess(this)) return;         //LeakCanary.install(this);         initFirebasePersistance();         appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();         appComponent.inject(this);         FrescoUtil.init(getApplicationContext());     }       public AppComponent getComponent() {         return appComponent;     } } 

1 Answers

Answers 1

  1. In your module you have a method that provides an instance of SoundsRepository - good
  2. In your AppComponent you are missing a:

    SoundsRepository soundsRepository(); 
  3. In your Global which extends Application/MultidexApplication you create your DaggerAppComponent - good

  4. In your other activities/fragments/services just call:

    Global application = (Global) getApplication(); SoundsRepository sr = application.getComponent().soundsRepository() 

Android guarantees you have only one instance of your Application (Global) class for all other actvities/services (its somewhat like a singleton).

So keep your component in that application class, and whenever you need your class, call: (YourApplication) getApplication().getComponent().yourSingleInstanceSomething();

I created and tested sample code for you: https://github.com/zakrzak/StackDaggerTest

Dagger's @Singleton is just a scope, and does not guarantee returning a singular instance of a class.

In my understanding, if you:

void inject(PostView postView); 

you tell Dagger to make everything you annotated with @Provided in AppModule accessible in your PostView as soon as you request it with:

@Inject SoundsRepository soundsRepository; 

then dagger just calls the @provided method which in your case returns a new SoundRepository instance:

@Singleton @Provides SoundsRepository provideSoundsRepository(Context ........) {     return new SoundsRepository(...); } 

which causes your problem

Read More

Wednesday, June 29, 2016

Dagger 2 lifecycle of a component, module and scope

Leave a Comment

I've read a lot of posts and tutorials about dagger 2:

http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

https://github.com/codepath/android_guides/wiki/Dependency-Injection-with-Dagger-2

http://fernandocejas.com/2015/04/11/tasting-dagger-2-on-android/

https://github.com/konmik/konmik.github.io/wiki/Snorkeling-with-Dagger-2

What determines the lifecycle of a component (object graph) in Dagger 2?

etc.

But I am still confused about the lifecycle of a component, and how it relates to module and scope. I want to make sure I don't create multiple instances of an object when I only want a Singleton. Hope someone can shed some light on these:

What's the lifecycle of a component that's built in the application class?

What's the lifecycle of a component that's built in the Activity or Fragment class?

If I want a singleton instance from a component, do I must annotate the component with @Singleton or a custom made scope and build that component in the application class?

If I build a component in the application class, does that mean all the object instances available through this component will be a singleton instance throughout the app until the app is killed or restarted?

I have a component with a custom scope let's say @ActivityScope, and I build that component in an Activity, will the object instances injected through this component be destroyed automatically after this activity's onDestroy() is called?

Again I have a component with a custom scope let's say @ActivityScope, and I build this component in ActivityA and ActivityB, will ActivityA and ActivityB share the same object instances from this component or they will have their own instances of the same object?

0 Answers

Read More

Saturday, March 12, 2016

Is this a correct way to use Dagger 2 for Android app in unit test to override dependencies with mocks/fakes?

Leave a Comment

For 'regular' Java project overriding the dependencies in the unit tests with mock/fake ones is easy. You have to simply build your Dagger component and give it to the 'main' class that drives you application.

For Android things are not that simple and I've searched for a long time for decent example but I was unable to find so I had to created my own implementation and I will really appreciate feedback is this a correct way to use Dagger 2 or there is a simpler/more elegant way to override the dependencies.

Here the explanation (project source can be found on github):

Given we have a simple app that uses Dagger 2 with single dagger component with single module we want to create android unit tests that use JUnit4, Mockito and Espresso:

In the MyApp Application class the component/injector is initialized like this:

public class MyApp extends Application {     private MyDaggerComponent mInjector;      public void onCreate() {         super.onCreate();         initInjector();     }      protected void initInjector() {         mInjector = DaggerMyDaggerComponent.builder().httpModule(new HttpModule(new OkHttpClient())).build();          onInjectorInitialized(mInjector);     }      private void onInjectorInitialized(MyDaggerComponent inj) {         inj.inject(this);     }      public void externalInjectorInitialization(MyDaggerComponent injector) {         mInjector = injector;          onInjectorInitialized(injector);     }      ... 

In the code above: Normal application start goes trough onCreate() which calls initInjector() which creates the injector and then calls onInjectorInitialized().

The externalInjectorInitialization() method is ment to be called by the unit tests in order to set the injector from external source, i.e. a unit test.

So far, so good.

Let's see how the things on the unit tests side looks:

We need to create MyTestApp calls which extends MyApp class and overrides initInjector with empty method in order to avoid double injector creation (because we will create a new one in our unit test):

public class MyTestApp extends MyApp {     @Override     protected void initInjector() {         // empty     } } 

Then we have to somehow replace the original MyApp with MyTestApp. This is done via custom test runner:

public class MyTestRunner extends AndroidJUnitRunner {     @Override     public Application newApplication(ClassLoader cl,                                       String className,                                       Context context) throws InstantiationException,             IllegalAccessException,             ClassNotFoundException {           return super.newApplication(cl, MyTestApp.class.getName(), context);     } } 

... where in newApplication() we effectively replace the original app class with the test one.

Then we have to tell the testing framework that we have and want to use our custom test runner so in the build.gradle we add:

defaultConfig {     ...     testInstrumentationRunner 'com.bolyartech.d2overrides.utils.MyTestRunner'     ... } 

When a unit test is run our original MyApp is replaced with MyTestApp. Now we have to create and provide our component/injector with mocks/fakes to the app with externalInjectorInitialization(). For that purpose we extends the normal ActivityTestRule:

@Rule public ActivityTestRule<Act_Main> mActivityRule = new ActivityTestRule<Act_Main>(         Act_Main.class) {       @Override     protected void beforeActivityLaunched() {         super.beforeActivityLaunched();          OkHttpClient mockHttp = create mock OkHttpClient          MyDaggerComponent injector = DaggerMyDaggerComponent.                 builder().httpModule(new HttpModule(mockHttp)).build();          MyApp app = (MyApp) InstrumentationRegistry.getInstrumentation().                 getTargetContext().getApplicationContext();          app.externalInjectorInitialization(injector);      } }; 

and then we do our test the usual way:

@Test public void testHttpRequest() throws IOException {     onView(withId(R.id.btn_execute)).perform(click());      onView(withId(R.id.tv_result))             .check(matches(withText(EXPECTED_RESPONSE_BODY))); } 

Above method for (module) overrides works but it requires creating one test class per each test in order to be able to provide separate rule/(mocks setup) per each test. I suspect/guess/hope that there is a easier and more elegant way. Is there?

This method is largely based on the answer of @tomrozb for this question. I just added the logic to avoid double injector creation.

2 Answers

Answers 1

1. Inject over dependencies

Two things to note:

  1. Components can provide themselves
  2. If you can inject it once, you can inject it again (and override the old dependencies)

What I do is just inject from my test case over the old dependencies. Since your code is clean and everything is scoped correctly nothing should go wrong—right?

The following will only work if you don't rely on Global State since changing the app component at runtime will not work if you keep references to the old one at some place. As soon as you create your next Activity it will fetch the new app component and your test dependencies will be provided.

This method depends on correct handling of scopes. Finishing and restarting an activity should recreate its dependencies. You therefore can switch app components when there is no activity running or before starting a new one.

In your testcase just create your component as you need it

// in @Test or @Before, just inject 'over' the old state App app = (App) InstrumentationRegistry.getTargetContext().getApplicationContext(); AppComponent component = DaggerAppComponent.builder()         .appModule(new AppModule(app))         .build(); component.inject(app); 

If you have an application like the following...

public class App extends Application {      @Inject     AppComponent mComponent;      @Override     public void onCreate() {         super.onCreate();         DaggerAppComponent.builder().appModule(new AppModule(this)).build().inject(this);     } } 

...it will inject itself and any other dependencies you have defined in your Application. Any subsequent call will then get the new dependencies.


2. Use a different configuration & Application

You can chose the configuration to be used with your instrumentation test:

android { ...     testBuildType "staging" } 

Using gradle resource merging this leaves you with the option to use multiple different versions of your App for different build types.

Move your Application class from the main source folder to the debug and release folders. Gradle will compile the right source set depending on the configuration. You then can modify your debug and release version of your app to your needs.

If you do not want to have different Application classes for debug and release, you could make another buildType, used just for your instrumentation tests. The same principle applies: Duplicate the Application class to every source set folder, or you will receive compile errors. Since you would then need to have the same class in the debug and rlease directory, you can make another directory to contain your class used for both debug and release. Then add the directory used to your debug and release source sets.

Answers 2

There is a simpler way to do this, even the Dagger 2 docs mention it but they don't make it very obvious. Here's a snippet from the documentation.

@Provides static Pump providePump(Thermosiphon pump) {     return pump; } 

The Thermosiphon implements Pump and wherever a Pump is requested Dagger injects a Thermosiphon.

Coming back to your example. You can create a Module with a static boolean data member which allows you to dynamically switch between your real and mock test objects, like so.

@Module public class HttpModule {      private static boolean isMockingHttp;      public HttpModule() {}      public static boolean mockHttp(boolean isMockingHttp) {         HttpModule.isMockingHttp = isMockingHttp;     }      @Provides     HttpClient providesHttpClient(OkHttpClient impl, MockHttpClient mockImpl) {         return HttpModule.isMockingHttp ? mockImpl : impl;     }  } 

HttpClient can be the super class which is extended or an interface which is implemented by OkHttpClient and MockHttpClient. Dagger will automatically construct the required class and inject it's internal dependencies just like Thermosiphon.

To mock your HttpClient, just call HttpModule.mockHttp(true) before your dependencies are injected in your application code.

The benefits to this approach are:

  • No need to create separate test components since the mocks are injected at a module level.
  • Application code remains pristine.
Read More