google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.44k stars 2.02k forks source link

Injecting Dependencies From Base Class #73

Closed f2prateek closed 9 years ago

f2prateek commented 9 years ago

In Dagger 1, you could call inject from a base class and have all dependencies satisfied for subclasses:

class BaseActivity extends Activity {
  @Inject Foo foo;

  public void onCreate(Bundle savedInstanceState) {
    objectGraph().inject(this);
  }
}

class MainActivity extends BaseActivity {
  @Inject Bar bar;

  @Override void onCreate(Bundle savedInstanceState) {
    super.onCreate();
    // both foo and bar will be available here
  }
}

This has changes in Dagger 2; injecting from a base class will only satisfy the base class's dependencies, not it's subclasses's.

class BaseActivity extends Activity {
  @Inject Foo foo;

  public void onCreate(Bundle savedInstanceState) {
    component().inject(this);
  }
}

class MainActivity extends BaseActivity {
  @Inject Bar bar;

  @Override void onCreate(Bundle savedInstanceState) {
    super.onCreate();
    // only foo is available here, not bar
  }
}

Is there a recommended pattern for injecting dependencies from a base class, rather than calling inject in each of the subclasses?

f2prateek commented 9 years ago

I should clarify that component has methods for both the subclass and base class.

public interface MyComponent {
  void inject(BaseActivity activity);

  void inject(MainActivity activity);
}
JakeWharton commented 9 years ago

So what's the question or what doesn't work? On Nov 16, 2014 1:22 PM, "Prateek Srivastava" notifications@github.com wrote:

I should clarify that component has methods for both subclass and base class.

public interface MyComponent { void inject(BaseActivity activity);

void inject(MainActivity activity); }

— Reply to this email directly or view it on GitHub https://github.com/google/dagger/issues/73#issuecomment-63240070.

f2prateek commented 9 years ago

Yeah accidentally hit enter too soon, but is there a pattern for injecting dependencies from a base class, rather than calling inject in each of the subclasses manually.

gk5885 commented 9 years ago

Here's how members injection methods work:

  1. You can make a members injection method for any type that has @Inject anywhere in its class hierarchy. If it doesn't, you'll get an error.
  2. All @Injected members in the entire type hierarchy will be injected: the argument type and all supertypes
  3. No members will be @Injected for subtypes of the argument type.

So, given the following:

final class Dep {
  @Inject Dep() {}
}

class A {
  @Inject Dep a;
}

class B extends A {
  @Inject Dep b;
}

class C extends B {
  @Inject Dep c;
}

@Component
interface TestComponent {
  B injectB(B instance);
}

The following test will pass:

TestComponent component = Dagger_TestComponent.create();
C instance = component.injectB(new C());
assertThat(instance.a).isNotNull();
assertThat(instance.b).isNotNull();
assertThat(instance.c).isNull();
JakeWharton commented 9 years ago

@f2prateek,

In Dagger 1, you could call inject from a base class and have all dependencies satisfied for subclasses.

This is true iff you included all subclasses on a module's injects= which was included in the graph with which you were injecting.

Dagger 2 is no different. You need to include all subclasses in a component so that the generated code can be wired to fulfill the contracts.

rlei commented 9 years ago

so this means in Dagger 2 we'll have to explicitly call inject in each of my Activity classes like the following:

    @Singleton
    @Component
    interface AppComponent {
        BaseActivity inject(BaseActivity activity);
        SomeActivity inject(SomeActivity activity);
    }

    class BaseActivity extends Activity {
        [snip]
        void onCreate() {
            super.onCreate(savedInstanceState);
            appComponent = Dagger_AppComponent.builder()
                [snip]
               .build();
            }
        }
        AppComponent getComponent() {
             return appComponent;
        }
    }

    class SomeActivity extends BaseActivity {
        @Inject Dep dep;
        void onCreate() {
            super.onCreate(savedInstanceState);
            getComponent().inject(this);
        }
    }

In Dagger 1 it's only needed to call inject in BaseActivity and the injection to subclass instances are all handled. I can live with Dagger 2's more explicit injection but it would be a bit simpler to convert existing projects with Dagger 1 way.

JakeWharton commented 9 years ago

@rlei No, it doesn't. It can still be in the base class.

rlei commented 9 years ago

@JakeWharton no it doesn't work, as far as I tested.

Basically what I did is in my last comment. Putting getComponent().inject(this) in BaseActivity.onCreate() only injects for BaseActivity. I had to add getComponent().inject(this) to each subclass onCreate() individually to let the injection happen.

My module and component code is as following:

    @Module
    public class AppModule {
        [snip]
        public AppModule(Application application) {
            this.application = application;
        }
        @Provides @Singleton
        DaoSession provideDaoSession() {
            [snip]
        }
    }

    @Singleton
    @Component(modules = AppModule.class)
    public interface AppComponent {
        void inject(MyApplication application);
        void inject(BaseActivity activity);
        void inject(ExpenseDetailsActivity activity);
        void inject(MainActivity activity);
        [snip]
    }

Did I miss anything?

[EDIT]: also updated my last comment.

JakeWharton commented 9 years ago

Ah, right right. This is in the JLS.

@gk5885 The difference in your example is that it would be a method in A calling inject(this) rather than one knowing about C.

Here's a fully working example of the problem:

abstract class BaseThing {
  @Inject String foo;

  void doIt() {
    Dagger_BaseComponent.create().inject(this);
  }
}
class RealThing extends BaseThing {
  @Inject CharSequence bar;
}

@Module
class BaseModule {
  @Provides String provideString() { return "String!"; }
  @Provides CharSequence provideCharSequence() { return "CharSeq!"; }
}

@Component(modules = BaseModule.class)
interface BaseComponent {
  void inject(BaseThing baseThing);
  void inject(RealThing realThing);
}

public class BaseExample {
  public static void main(String... args) {
    RealThing realThing = new RealThing();
    realThing.doIt();
    System.out.println("String? => " + realThing.foo);
    System.out.println("CharSeq? => " + realThing.bar);
  }
}

This was a common pattern in Dagger 1.

gk5885 commented 9 years ago

Right, this will definitely be a change from how things were done in D1. Essentially, the ObjectGraph used to do a bunch of reflection to figure out the type of the object being injected and dispatch to the right logic. You could do exactly the same thing if you wanted, but the cost of the little extra bit of verbosity makes for a much more efficient code path.

Plus, in experimenting with Android apps in D2 we found that infrastructure around managing components (as opposed to ObjectGraphs) really wasn't worth its weight. We had apps with a base activity that held a reference to a graph, but when we moved it to D2 it just really didn't have as much value since the components are strongly typed and often more granular (i.e. you may end up with separate activity-scoped components per activity rather than a huge graph for all activities). Take a look at https://github.com/gk5885/dagger-android-sample/ and see if the pattern in that (albeit trivial) example works for you.

f2prateek commented 9 years ago

@gk5885 That sounds good to me! It should be something that should be mentioned in the migration guide when it is released.

serandel commented 9 years ago

@f2prateek I made an abstract method in my BaseActivity that creates the Component and calls inject. That way each concrete class only have to do something like Dagger_FooComponent.create().inject(this).

pakoito commented 9 years ago

What is the use case here for library projects? I have a library project with methods accepting and abstract class containing a field the user will need to do some work. That field has to be injected from the library at runtime. The programmer can't go and add those lines of code to the component interface.

gk5885 commented 9 years ago

@pakoito, I'm not quite following what you're asking. Can you show some example code?

pakoito commented 9 years ago

In a library module:

public abstract class BaseSystem {
  @Inject
  protected World projectWorld;
}

@Singleton
public class World {
 @Inject
  public World(){} 

 public void hey(){}
}

public class Framework {
    @Component
    public interface MyComponent { 
        void inject(BaseSystem system);
    } 

  public Framework(BaseSystem mySystem) {
     // Ideally, but not current
     component.inject(mySystem);
  } 
}

And then in my impl module:

public abstract class ImplSystem extends BaseSystem {
  public void doWork(){
      projectWorld.hey();
  }

At the impl module we have no access to MyComponent to add new classes to be injected.

pakoito commented 9 years ago

My current implementation uses Artemis-odb for this injections, but they use Reflection so it's a tradeoff Dagger may not want to make.

svenjacobs commented 9 years ago

Performing the dependency injection in an abstract base class of Activity, Fragment, etc. seems to be a common pattern in Android development with Dagger 1 because it's very handy. I'm using it, too.

How this can be achieved with Dagger 2 respectively alternative patterns should be clearly documented in a (migration) guide.

svenjacobs commented 9 years ago

Maybe injecting dependencies in a base class would work if a component supports generics? See #65.

tbroyer commented 9 years ago

FYI, I've just pushed out a 1.0-SNAPSHOT of Bullet • to the Sonatype OSS repository, so you can make @JakeWharton's example above work (well, it should work, I haven't tested):

abstract class BaseThing {
  @Inject String foo;

  void doIt() {
    // Wrap the Component into the generated ObjectGraph:
    new Bullet_BaseComponent(Dagger_BaseComponent.create()).inject(this);
  }
}

class RealThing extends BaseThing {
  @Inject CharSequence bar;
}

@Module
class BaseModule {
  @Provides String provideString() { return "String!"; }
  @Provides CharSequence provideCharSequence() { return "CharSeq!"; }
}

@Component(modules = BaseModule.class)
interface BaseComponent {
  // You don't need to include a method for BaseThing here, only your concrete subclasses.
  void inject(RealThing realThing);
}

public class BaseExample {
  public static void main(String... args) {
    RealThing realThing = new RealThing();
    realThing.doIt();
    System.out.println("String? => " + realThing.foo);
    System.out.println("CharSeq? => " + realThing.bar);
  }
}
cgruber commented 9 years ago

This is less an issue, and more a discussion - I'm not sure where to go from here, but I'm going to close the issue for now, and if there's a feature request and/or further relevant discussion, it can be reopened. Given that #102 seems to be related, maybe further discussion can happen there.

ralph-bergmann commented 9 years ago

@JakeWharton your example does not work for me.

The DaggerBaseComponent.create().inject(this) call in BaseThing uses the inject(BaseThing baseThing)method and not the inject(RealThing realThing) so realThing.bar is null.

I fixed it with a doIt()method in RealThing.

But I have another question.

How does the code look like if RealThing want's to add it's own Component / Modul? If I create a RealComponent / Modul the BaseThing injection doesn't work. Do I have to add all subclass injections in the BaseModul?

mcfongtw commented 5 years ago

Tried the example posted by @JakeWharton, and member in SubClass was not injected. However, as I explore more in the generated code, that could possibly be done via the following:

abstract class BaseThing {
    @Inject
    String foo;

    public BaseThing() {
        this(DaggerBaseComponent.create());
    }

    public BaseThing(BaseComponent component) {
        component.inject(this);
    }
}
class RealThing extends BaseThing {
    @Inject CharSequence bar;

    public RealThing() {
        this(DaggerBaseComponent.create());
    }

    public RealThing(BaseComponent component) {
        component.inject(this);
    }
}

@Module
class BaseModule {
    @Provides
    String provideString() { return "String!"; }
    @Provides CharSequence provideCharSequence() { return "CharSeq!"; }
}

@Component(modules = BaseModule.class)
interface BaseComponent {
    void inject(BaseThing baseThing);
    void inject(RealThing realThing);
}

public class BaseExample {
    public static void main(String... args) {
        RealThing realThing = new RealThing();

        System.out.println("String? => " + realThing.foo);
        System.out.println("CharSeq? => " + realThing.bar);
    }
}

This way, bar is injected properly.

vinhnemo commented 1 year ago
@Singleton
@Component(modules = {ABC.class})
public interface BaseComponent<T extends Base> {
    void inject(T handler);
}

I tried to do something like this but compiled error. Any approach for this? Thanks

bcorso commented 1 year ago

@vinhnemo Dagger's job is to generate an implementation of your interface, but Dagger has no idea what T is so there's no way for Dagger to know how to "inject" T.

You probably want to start a new issue since this seems like a different question, and please add more details about what you're actually trying to accomplish.