I have large Android ViewModel classes that tend to have a lot of dependencies (most of them are DAOs from Room, one per SQLite table). Some have more than 10 dependencies.
This is fine but the #Inject constructor is bloated with arguments, and contains only boilerplate code to set the injected members from the constructor arguments.
I wanted to switch to "regular" injected members, identified individually with an #Inject annotation, like other (dumb) classes.
This fails for Android related classes (although ViewModels are advertised as non-Android dependent, e.g. they don't use the Android framework) such as activities and fragments.
The workaround for that is to use a factory, which is injected from the Application class using the nice HasActivityInjector, HasServiceInjector, etc. interfaces.
Dagger doesn't provide any HasViewModelInjector, so if I persist in injecting members individually instead of injecting the constructor, here's what I'm given:
error: [dagger.android.AndroidInjector.inject(T)] XXXViewModel cannot be provided without an #Inject constructor or from an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
If I create a module that has a #Provides annotation to create the ViewModel, this doesn't inject individual members.
Did I miss something (my last sentence is what's most important in my question) or is it simply not possible to inject members, and I have to inject the constructor?
A bit of code.
What I want:
class MyViewModel extends ViewModel {
#Inject
MyDao myDao;
}
versus what I need to do:
class MyViewModel extends ViewModel {
private final MyDao myDao;
#Inject
MyViewModel(MyDao myDao) {
this.myDao = myDao;
}
}
First block of code (what I want) requires this method in a module:
#Provides
MyViewModel provideMyViewModel() {
return new MyViewModel();
}
but in this case the myDao field is null. How to inject the #Inject-annotated members?
I want to avoid the use of the 2nd block of code, which tends to create a huge constructor bloated with many arguments, should I need to inject a lot of members.
There are multiple ways of injection and I think you are referring to field injection. Field injection, unlike constructor injection, must be triggered manually. To do that, define a method in your component with the view model as parameter.
void inject(ViewModel viewModel)
And then call this method from your view model constructor perhaps.
class MyViewModel extends ViewModel {
private final MyDao myDao;
#Inject
MyDao myDao;
public MyViewModel() {
MyComponent mycomponent = DaggerMyComponent.....
myComponent.inject(this);
}
}
Related
Hilt is not supportting non epmty constructor modules. If we need to migrate partially to Hilt from dagger , how we can inject dependencies from legacy dagger modules having non empty constrctors to hilt components such as HiltViewModel.
// Legacy Dagger module
#Module
public class DaggerModule {
private final Boolean customBoolean;
DaggerModule(Boolean customBoolean) {
this.customBoolean = customBoolean;
}
#Provides
#Singleton
CustomClass provideCustomClass() {
return CustomClass(customBoolean);
}
}
#Module
public class AnotherDaggerModule {
#Provides
#Singleton
AnotherClassDepndsOnCustomClass provideAnotherClass(CustomClass customClass) {
return AnotherClassDepndsOnCustomClass(customClass);
}
}
// Migrated Hilt module
#HiltViewModel
class HiltViewModel #Inject constructor(
private val anotherClass: AnotherClassDepndsOnCustomClass
) : ViewModel() {
...
}
Since we are not using components to pass some custom parameters while initialising modules, is there any solution which I'm not aware already exists?
While running the app, the app crashing with error DaggerModule must be set.
You'll need to refactor.
The documentation on Migrating to Hilt describes this case under the heading "Handling Component Arguments", since instantiable modules would otherwise be treated as component arguments passed through a Builder or Factory:
Hilt components cannot take component arguments because the initialization of the component is hidden from users. [...]
If your component has any other arguments either through module instances passed to the builder or #BindsInstance, read this section on handling those. Once you handle those, you can just remove your #Component.Builder interface as it will be unused.
Under "Component arguments" the Hilt documentation confirms that a refactor is required:
Because component instantiation is hidden when using Hilt, it is not possible to add in your own component arguments with either module instances or #BindsInstance calls. If you have these in your component, you’ll need to refactor your code away from using these.
You can consider some of these structures:
Replacement module / subclassing Modules
In your example, you might need to create a replacement Module that provides a binding for CustomClass. This might be as straightforward as subclassing the Module and providing a public no-arg constructor that provides the super(value) constructor call your module needs. If your Module would only ever get a single value in your graph (but might get a different value in a separate application), then this might be enough.
#Module
public class AdaptedDaggerModule extends DaggerModule {
AdaptedDaggerModule() {
super(true);
}
}
Note that module subclasses are somewhat limited in utility, and should not be used for testing overrides.
Custom subcomponents
However, you also wrote "components are created in respective modules where we need to use" in a comment, and you can continue doing so using custom subcomponents in Hilt, with more comprehensive documentation in javadoc or the main Dagger subcomponent documentation. Because you would create this component through an explicit call to a Builder or Factory, you could provide the Module instance there. Subcomponents inherit bindings from their parent components, so you could avoid specifying your entire list of Modules.
Note that doing this as a subcomponent is mostly valuable when you have a dense tree with multiple references to the instance you're providing in the constructor. If this is simply a matter of combining graph-based constructor arguments with one-off constructor arguments, assisted injection is probably a better option.
/** Subcomponents are usually declared on modules. You can also reuse one you have. */
#Module(subcomponents={YourSubcomponent.class})
public interface IncludeThisInYourHiltModuleList {}
#Subcomponent(modules={DaggerModule.class, AnotherDaggerModule.class})
public interface YourSubcomponent {
AnotherClassDepndsOnCustomClass anotherClass();
#Subcomponent.Builder
interface Builder {
Builder daggerModule(DaggerModule daggerModule); // arbitrary name
YourSubcomponent build(); // arbitrary name
}
}
#HiltViewModel
class HiltViewModel #Inject constructor(
private val yourSubcomponentBuilder: YourSubcomponent.Builder
) : ViewModel() {
fun yourMethod() {
val subcomponent =
yourSubcomponentBuilder.daggerModule(DaggerModule(false)).build()
val anotherClass = subcomponent.anotherClass()
// ...
}
}
Constructor values in Hilt-managed components
The most difficult case would be where your Module would want separate values in each of your Hilt-managed components, e.g. each Activity needing to pass a different constructor argument. In that case you might need to rephrase the customBoolean (or other parameters) as deriving the value from the Activity instance itself. This maintains Hilt's expectation that it can create an Activity component for each Activity instance that Android unpredictably creates or recreates, and it can do so without specifying any other constructor parameters.
Whats the difference between #Inject and #Provide ?
although both are used for providing dependencies then whats the difference ?
This is covered very well in documentation, #Inject and #Provides are two different ways of introducing dependencies in the dependency graph. they are suited for different use cases
#Inject
Easy to use, simply add #Inject on constructor or property and you are done
It can be used to inject types as well as type properties
In a subjective way it may seem clearer than #Provides to some people
#Provides
If you don't have access to source code of the type that you want to inject then you can't mark its constructor with #Inject
In some situations you may want to configure an object before you introduce it in dependency graph, this is not an option with #Inject
Sometimes you want to introduce an Interface as a dependency, for this you can create a method annotated with #Provides which returns Inteface type
Following are the examples of above three points for #Provides
If you can't access source code of a type
// You can't mark constructor of String with #Inject but you can use #Provides
#Provides
fun provideString(): String {
return "Hello World"
}
Configure an object before introducing in the dependency graph
#Provides
fun provideCar(): Car {
val car = Car()
// Do some configuration before introducing it in graph, you can't do this with #Inject
car.setMaxSpeed(100)
car.fillFuel()
return car
}
Inject interface types in dependency graph
interface Logger { fun log() }
class DiscLogger : Logger{ override fun log() { } }
class MemoryLogger : Logger { override fun log() { } }
#Provides
fun provideLogger(): Logger {
val logger = DiscLogger() \\ or MemoryLogger()
return logger
}
#Inject:- It is used to inject dependencies in class.
#provides:- Required to annotated the method with #provide where actual instance is created.
This has covered in Dagger-2 documentation clearly.
What #Inject can Do:
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.
If your class has #Inject-annotated fields but no #Inject-annotated
constructor, Dagger will inject those fields if requested, but will
not create new instances. Add a no-argument constructor with the
#Inject annotation to indicate that Dagger may create instances as
well.
Similarly dagger can create for methods also.
Classes that lack #Inject annotations cannot be constructed by
Dagger.
What #Inject can't Do: and can be used #Provides annotation
Interfaces can’t be constructed.
Third-party classes can’t be annotated.
Configurable objects must be configured!
im creating a highly modular application, i have a lot of clases that need to be injected, all of them are childs (not direct childs) of the same class, none of them have constructor parameters.
I want to avoid having to create a "#Provides" method for each one of them in my module.
Is there a way to tell dagger to automatically provide all the classes that implement a base interface? Or is it possible to do it myself using reflection?
Im using dagger-android with kotlin
Update: Ill post some code to illustrate
In one of the modules i have this interface
interface ExampleClass: BaseExample {
fun doSomething()
}
}
Then in the main app i implement it
class ExampleClassImpl #Inject constructor() : ExampleClass {
override fun doSomething(){
}
}
The class where i need it is a Viewmodel created with dagger so inject works on the constructor.
class ExampleViewModel #Inject constructor(val exmpl :ExampleClass) : BaseViewModel {
}
I want to inject that ExampleClassImpl, to do that i need to create a #module with a method annotated with #Provides or #Bind and return that class.
Without the provider i get an error at compile time:
error: [Dagger/MissingBinding] com.myapp.ExampleClassImpl cannot be provided without an #Provides-annotated method.
You want to inject ExampleClass, but Dagger only knows about ExampleClassImpl. How would Dagger know that you want that specific subclass?
Moreover, you say you have many subclasses that you want to inject. How would Dagger know which one to provide to a constructor expecting the base class?
If you want ExampleViewModel to get an instance of ExampleClassImpl then you can simply change the declaration to:
class ExampleViewModel #Inject constructor(val exmpl :ExampleClassImpl)
Doing so you lose the ability to swap the constructor argument with a different implementation of ExampleClass.
The alternative is to have one #Named #Provides method per subclass. So something like:
// In your module
#Provides
#Named("impl1")
fun provideExampleClassImpl1(impl: ExampleClassImpl): ExampleClass = impl
// When using the named dependency
class ExampleViewModel #Inject constructor(#Named("impl1") val exmpl :ExampleClass)
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.
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.
I provide ImagesRepo using RepoModule, ImgesReo depends on RxApiController and SharePreferenceHelper and i am providing these dependencies in RepoModule itself, these dependencies comes from AppModule.
#Module(includes = AppModule.class)
public class RepoModule {
#Provides
#Inject
public ImagesRepo providesImagesRepo(RxApiController rxApiController, SharePreferenceHelper sharePreferenceHelper) {
return new ImagesRepo(rxApiController, sharePreferenceHelper);
}
}
When i try to inject ImagesRepo like this
#Inject
public ImagesRepo imagesRepo;
public MyActivityViewmodelImpl() {
MyApplication.getRepoComponent().inject(this);
}
It shows error if i remove #Inject from constructor of ImagesRepo, I think that i am providing RxApiController and SharePreferenceHelper from RepoModule
#Inject
public ImagesRepo(RxApiController rxApiController, SharePreferenceHelper sharePreferenceHelper) {
super(rxApiController, sharePreferenceHelper);
}
Question is why i am suppose to add #Inject at ImagesRepo constructor, if i am providing dependencies for ImagesRepo in RepoModule itself
To avoid errors and confusion you should make sure to understand what each annotation does. A #Provides method does not need an #Inject annotation. Here, you could even use constructor injection alone, and you wouldn't need the #Provides method (or the module) at all, reducing the amount of boilerplate needed.
As to your specific error, I would guess that you either don't add RepoModule to your component, or that you try to inject your class with the wrong component (that does not have access to RepoModule).
Adding #Inject on the constructor of ImagesRepo will mark it for constructor injection so that Dagger can and will create it for you. There is no need for the module (that you didn't add / component can't access), which is why it will "work" when you do so.
To avoid confusion and errors either use a #Provides method from a module or use constructor injection—preferrably constructor injection which will eliminate the boilerplate, which is one of the reasons why you'd use Dagger in the first place.