21

In order to learn Dagger 2 i decided to rewrite my application but I'm stuck with finding the proper solution for the following problem.

For the purpose of this example let's assume we have an interface called Mode:

public interface Mode {
    Object1 obj1();

    //some other methods providing objects for app
}

and two implementations: NormalMode and DemoMode.

Mode is stored in singleton so it could be accessed from anywhere within application.

public enum ModeManager {
  INSTANCE,;

  private Mode mode;

  public Mode mode() {
    if (mode == null)
      mode = new NormalMode();
    return mode;
  }

  public void mode(Mode mode) { //to switch modules at runtime
    this.mode = mode;
  }
}

The NormalMode is switched to DemoMode at runtime (let's say, when user clickcs on background couple of times)

public void backgroundClicked5Times(){
  ModeManager.INSTANCE.mode(new DemoMode());
  //from now on every object that uses Mode will get Demo implementations, great!
}

So first I got rid of the singleton and defined Modes as Dagger 2 modules:

@Module
public class NormalModeModule {
  @Provides
  public Object1 provideObject1() {
    return new NormalObject1();
  }
}

@Module
public class DemoModeModule {
  @Provides
  public Object1 provideObject1() {
    return new DemoObject1();
  }
}

Now in the method backgroundClicked5Times instead of dealing with singleton I would like to replace NormalModeModule with DemoModeModule in DAG so the other classes that need Object1 would get a DemoObject1 implementation from now on.

How can I do that in Dagger?

Thanks in advance.

Marcin
  • 893
  • 7
  • 19
  • Possible duplicate of [Swappable modules with Dagger 2](https://stackoverflow.com/questions/35658488/swappable-modules-with-dagger-2) – tir38 Jun 27 '17 at 19:39

2 Answers2

5

Maybe you can consider using multibindings?

@Module
public class NormalModeModule {
  @Provides
  @IntoMap
  @StringKey("normal")
  public Object1 provideObject1() {
    return new NormalObject1();
  }
}

@Module
public class DemoModeModule {
  @Provides
  @IntoMap
  @StringKey("demo")
  public Object1 provideObject1() {
    return new DemoObject1();
  }
}

and when using Mode:

@Inject
Map<String, Mode> modes;
//or you perfer lazy initialization:
Map<String, Provider<Mode>> modes;

public void backgroundClicked5Times(){
  ModeManager.INSTANCE.mode(modes.get("demo"));
  //if you are using Provider:
  ModeManager.INSTANCE.mode(modes.get("demo").get());
  //from now on every object that uses Mode will get Demo implementations, great!
}
ZumiKua
  • 506
  • 4
  • 10
4

Having experimented with dagger for a while I came up with solution that seems to be working well in my use case.

  1. Define class that will hold state information about mode

    public class Conf {
      public Mode mode;
    
      public Conf(Mode mode) {
        this.mode = mode;
      }
    
      public enum Mode {
        NORMAL, DEMO
      }
    }
    
  2. Provide singleton instance of Conf in Module

    @Module
    public class ConfModule {
      @Provides
      @Singleton
      Conf provideConf() {
        return new Conf(Conf.Mode.NORMAL);
      }
    }
    
  3. Add module to AppComponent

    @Singleton
    @Component(modules = {AppModule.class, ConfModule.class})
    public interface AppComponent {
        //...
    }
    
  4. Define modules that provide different objects based on Mode

    @Module
    public class Object1Module {
    
      @Provides
      Object1 provideObject1(Conf conf) {
        if (conf.mode == Conf.Mode.NORMAL)
          return new NormalObject1();
        else
          return new DemoObject1();
      }
    }
    
  5. To switch mode at runtime simply inject Conf object and modify it:

    public class MyActivity extends Activity {
        @Inject Conf conf;
    
        //...
    
        public void backgroundClicked5Times(){
            conf.mode = Conf.Mode.DEMO;
    
            //if you have dagger objects in this class that depend on Mode
            //execute inject() once more to refresh them
        }
    }
    
Marcin
  • 893
  • 7
  • 19
  • Seems like a decent way to do this, but this wont work when the provided dependency (that shall be replaced at runtime) is a singleton, will it? – Markus Ressel Jul 15 '17 at 13:27
  • 2
    Yes, if you'd annotate `provideObject1` with `@Singleton` it wouldn't work, but on the other hand it contradicts with the idea in this use case. If you want to replace objects at runtime then you cannot mark such a method with `@Singleton`. On the other hand you could make `NormalObject1` and `DemoObject1` singletons so the method providing dependencies wouldn't create more than 2 instances. – Marcin Jul 17 '17 at 08:36
  • That last sentence was the info I needed. Great solution, thx :) – Markus Ressel Jul 17 '17 at 19:56
  • 3
    This is exactly what I am looking for. But can you please elaborate more about the last comment - "If you have dagger objects in this class that depend on Mode execute inject() once more to refresh them". – Anuj Garg Aug 10 '18 at 07:23
  • I love this idea, treat module as a configuration – Chauyan Jul 18 '19 at 19:57