InflationInject icon indicating copy to clipboard operation
InflationInject copied to clipboard

MembersInjector for late injection

Open mlykotom opened this issue 5 years ago • 9 comments

AssistedInject only works for construction injection. Dagger on the other hand is able to inject stuff after construction with *_MembersInjector for some @Inject lateinit var dep: Dependency.

Is it somehow possible to have this for AssistedInject?

mlykotom avatar Dec 16 '19 14:12 mlykotom

This is not possible with the library, no. But you don't really need a library to do this. You can members inject the instance and then set whatever other fields you want directly.

The reason assisted inject only works for constructor injection is that you cannot ask Dagger to partially call a constructor and then partially supply the other arguments.

JakeWharton avatar Dec 16 '19 14:12 JakeWharton

  1. I understand that you don't need the library to do this and can somehow set fields manually, but since Dagger allows this, doesn't AssistedInject kind of breaks the feature?

  2. I'm not sure if I understand your second point.

But. In the generated factory, there could be added MembersInjector<TheClass> and after instantiating the class, just call injectMembers on it?

public final class TheClass_AssistedFactory implements TheClass.Factory {
  private final Provider<ConstructorDep> constructorDep;

  private final MembersInjector<TheClass> membersInjector;

  @Inject
  public TheClass_AssistedFactory(
        Provider<ConstructorDep> constructorDep,
        MembersInjector<TheClass> membersInjector) {
    this.constructorDep = constructorDep;
    this.membersInjector = membersInjector;
  }

  @Override
  public MViewModel create(AssistedDep assistedDep) {
    TheClass obj = new TheClass(
            constructorDep.get(),
            assistedDep);

    membersInjector.injectMembers(obj);
    return obj;
  }
}

Nice thing about this is, that if you don't have any @Inject lateinit var inside the class, Dagger will use NoOpMembersInjector.INSTANCE which does nothing and also if there's have inheritance, it will include the base member injectors.


Or shouldn't there be at least some warning?

mlykotom avatar Dec 16 '19 16:12 mlykotom

Oh, you're saying you want members injection to be performed on constructor-injected types? I see. I suppose that's reasonable for consistency of behavior.

I thought you wanted assisted injection on a type that was solely members injected.

Can I ask why you are doing this? Members injection is a truly terrible thing.

JakeWharton avatar Dec 16 '19 16:12 JakeWharton

I agree, members injection seems like a terrible thing.

The reason is android ViewModel with inheritance:

  • we have "pure" Dagger with ViewModels
  • all ViewModels inherit from some BaseViewModel class
  • for some historical reason (🤷‍♂ ) there's members injection in the BaseViewModel, therefore when inheriting from the base, it doesn't need to have inject param in all inherited constructors
  • we'd like to add AssistedInject to the project, so that we can use SavedStateHandle
  • if we do that, you never can access the members injection.

I know, that it seem like a terrible thing, but also if (for some reason) you have members injection without inheritance, it's the same - no warning, crashes at runtime.

I was checking briefly how the MembersInjector works and for specific class it includes even parent MembersInjector, e.g for SomeViewModel inheriting BaseViewModel, it properly injects SomeViewModel dependencies and also BaseViewModel dependencies.

This means, that injecting MembersInjector<TheClass> into generated factory should (?) be safe and sufficient.

mlykotom avatar Dec 16 '19 16:12 mlykotom

So one trouble here is that AssistedInject is not Dagger-specific and thus cannot depend on the MembersInjector type. It's purely a javax.inject-based injector, at least in the core.

JakeWharton avatar Dec 17 '19 02:12 JakeWharton

I see the issue. I was thinking, that it would be possible to extend the pure AssistedInject processor to generate the code, but then not sure, how to pass the information about what to generate.

a) pass some data from module to module somehow? b) AssistedInjectDagger2Processor would generate another annotation for the members injection which AssistedInjectProcessor would parse and generated the data

I don't like either option, but not sure how else could be solved

mlykotom avatar Dec 17 '19 10:12 mlykotom

FYI this is no longer a problem as AssistedInjection has moved to Dagger and all that's left is inflation injection. We can add special handling for MembersInjector now.

JakeWharton avatar Mar 26 '21 00:03 JakeWharton