GreyScreenOfMeh
GreyScreenOfMeh

Reputation: 273

Injecting ViewModel using Dagger 2 and trying to understand why @Binds doesn't work, when @Provides does

I'm trying to inject a ViewModelProvider.Factory and I'm having trouble understanding why I'm not able to use a @Binds annotation.

This annotation seems to work:

@Binds
abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory viewModelFactory);

Combined with the following annotation, the project compiles:

@Provides
@IntoMap
@ViewModelKey(MyViewModel.class)
static ViewModel MyViewModel(){
    return new MyViewModel();
}

However, if the above code is replaced with the following:

@Binds
@IntoMap
@ViewModelKey(MyViewModel.class)
abstract ViewModel bindMyViewModel(MyViewModel viewModel);

All of a sudden I get the following error message:

...MyViewModel cannot be provided without an @Inject constructor or an @Provides-annotated method.

Can someone explain why the first case works and the second doesn't? As I've understood @Binds, it should create a class of the return type, of the concrete implementation that's passed as a parameter.

Upvotes: 2

Views: 435

Answers (1)

Jeff Bowman
Jeff Bowman

Reputation: 95614

As egoldx discussed in the comments, you need to annotate your MyViewModel constructor with @Inject if you want Dagger to call it. As you mentioned, this means that JSR-330 defines @Inject to have multiple valid uses:

  • To mark constructors that the DI system should call to obtain an instance,
  • To mark fields that the DI system should populate after creation and on existing instances, and
  • To mark methods to call upon creation/injection, populating its method parameters from the DI system.

To be especially clear here, Dagger's behavior documented in the User's Guide:

Use @Inject to annotate the constructor that Dagger should use to create instances of a class. When a new instance is requested, Dagger will obtain the required parameters values and invoke this constructor.

...contradicts JSR-330's definition of @Inject, in that public parameterless constructors are not eligible to be called:

@Inject is optional for public, no-argument constructors when no other constructors are present. This enables injectors to invoke default constructors.

The discussion around this deviation is in Square's Dagger 1 repository, issue #412 and Google's Dagger 2 issue #1105. In summary, the extra clarity was deemed useful (particularly given the number of objects that you could accidentally inject with default constructors), the extra cost of adding @Inject was determined not to be too burdensome, and it avoids some ambiguity about whether subclasses are eligible for injection without having to inspect the entire class hierarchy for @Inject fields.

Upvotes: 1

Related Questions