Closed f2prateek closed 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);
}
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.
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.
Here's how members injection methods work:
@Inject
anywhere in its class hierarchy. If it doesn't, you'll get an error.@Inject
ed members in the entire type hierarchy will be injected: the argument type and all supertypes@Inject
ed 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();
@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.
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.
@rlei No, it doesn't. It can still be in the base class.
@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.
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.
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 ObjectGraph
s) 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.
@gk5885 That sounds good to me! It should be something that should be mentioned in the migration guide when it is released.
@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)
.
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.
@pakoito, I'm not quite following what you're asking. Can you show some example code?
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.
My current implementation uses Artemis-odb for this injections, but they use Reflection so it's a tradeoff Dagger may not want to make.
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.
Maybe injecting dependencies in a base class would work if a component supports generics? See #65.
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);
}
}
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.
@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?
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.
@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
@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.
In Dagger 1, you could call
inject
from a base class and have all dependencies satisfied for subclasses:This has changes in Dagger 2; injecting from a base class will only satisfy the base class's dependencies, not it's subclasses's.
Is there a recommended pattern for injecting dependencies from a base class, rather than calling inject in each of the subclasses?