Dagger is kicking my butt.
I have the following layout.
A MainActivity which is injected into the Object graph.
The MainActivity #Injects a MainPresenter interface. This is Provided via the MainModule which returns a concrete implementation.
The concrete MainPresenter implementation takes a FileContentInteractor interface. This is provided in the InteractorModule which returns a concrete FileContentInteractor implementation.
MainModule can do this because it includes InteractorModule.class
Up until now everything is great. From this point on is where it gets tricky.
The concrete FileContentInteractor implementation injects some member variables using #Inject. These member variables are all interfaces which are provided concrete implementations via their respective modules.
An example is #Inject ThreadExecutor threadExecutor. I thought that this would be provided because;
InteractorModule includes ExecutorModule.class.
ExecutorModule.class #Provides a concrete implementation of ThreadExecutor in the form of a TaasExecutor object.
TaskExecutor object has no injected dependencies.
When I run my app and the MainActivity opens the following happens;
MainActivity calls a method of MainPresenter implementation - works fine!
MainPresenter has a FileContentInteractor implementation and calls a method on it - works
The method in FileContentInteractor implementation tries to call a method in ThreadExecutor implementation (which is #Injected as a member variable). This failed because the #Injected ThreadExecutor implementation is null.
Can anyone help?
Ok so it turns out I was kicking my own butt...
I was referencing the concrete classes with my #Inject members but using the Interfaces as my #Provides.
Amending the #Inject members from classes to interfaces fixed it.
Related
I've multiple modules in my app, each one of them have his own Module for UI Injection.
Now, i want to have "feed" fragment that have a some pieces from other modules.
So i trying doing it using FragmentContainerView.
I want to Inject the fragment that defined on other module. I'm trying to "include" my other fragment, so i can navigate into this fragment, but if i'm try to inject it, i'm getting this following error:
error: [Dagger/MissingBinding] DotsFragment cannot be provided without an #Inject constructor or an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
Any idea how can i do it?
HomeModule:
#Module(
includes = [
DotsModule::class
]
)
abstract class HomeModule {
}
DotsModule:
#Module
abstract class DotsModule {
#FragmentScoped
#ContributesAndroidInjector
abstract fun contributeDotsFragment(): DotsFragment
}
HomeFragment:
#Inject
lateinit var dotsFragment: DotsFragment
Just push the Fragment into the container as described in the docs, and don't worry about the injection. You don't even need the explicit inclusion of DotsModule from HomeModule, though you may choose to keep it, especially if HomeModule is in a different Activity. DotsFragment will inject itself in onAttach.
Unlike most classes that use Dagger injection, Fragments can't be injected in their constructors: Android will only call the zero-arg or one-arg Fragment() constructor, so there isn't room for other constructor parameters. When you see your abstract fun contributeDotsFragment() function with #ContributesAndroidInjector, it is not the case that DotsFragment is available on your graph to be constructed or provided. Instead, Dagger does some magic1 that allows the Fragment to have its #Inject-labeled fields populated2 once you call AndroidSupportInjection.inject(this) in onAttach, which might happen in DaggerFragment if you inherit from that. Your Fragment has to assume that anything marked #Inject is null until the Fragment is attached.
Consequently, if you need an instance of DotsFragment, you can just call DotsFragment() or delegate to your FragmentManager's FragmentFactory (which will likely just call DotsFragment()). You can also use overloads to FragmentTransaction.replace(...) that take a Class<Fragment> rather than a Fragment instance.
The Fragment will inject itself later in onAttach, as it would if Android itself called the constructor. If you interact with the Fragment before that, be careful, as its #Inject-annotated fields will not be populated yet. If you need to interact with the Fragment in ways that use Dagger-provided instances, it may be best to create a Bundle that the Fragment can use to initialize itself in onCreate.
1: Behind the scenes, dagger.android creates a code-generated subcomponent instance, probably called DotsModule_BindDotsFragment.DotsFragmentSubcomponent, and registers that subcomponent as an AndroidInjector.Factory<DotsFragment> in a Map. The generated subcomponent receives all the modules you set on the #ContributesAndroidInjector annotation as well as all the scope annotations you set on the abstract fun. In onAttach (yours or DaggerFragment's), the call to AndroidSupportInjection.inject(this) reads from that Map, finds the Injector/Subcomponent, creates an instance, and uses that to inject the Fragment with its own set of #FragmentScope instances.
2 The #Inject-annotated methods will be called too. That's method injection, compared to field injection; the general term is members injection for all of the post-constructor initialization Dagger can do.
I'm injecting with Dagger-Hilt a class with a dependency on #ActivityContext in a ViewModel, this module is installed in ActivityComponent and scoped to activity and it's throwing me an error whenever I try to compile. For your information I have other modules with ActivityRetainedComponent and SingletonComponent injecting #ApplicationContext.
Now I'm trying to figure out what does this error mean.
error: [Dagger/MissingBinding] com.rober.fileshortcut_whereismyfile.utils.PermissionsUtils cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract static class SingletonC implements App_GeneratedInjector,
^
com.rober.fileshortcut_whereismyfile.utils.PermissionsUtils is injected at
com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel(�, permissionsUtils, �)
com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel is injected at
com.rober.fileshortcut_whereismyfile.ui.fragments.filefragment.FileViewModel_HiltModules.BindsModule.binds(vm)
#dagger.hilt.android.internal.lifecycle.HiltViewModelMap java.util.Map<java.lang.String,javax.inject.Provider<androidx.lifecycle.ViewModel>> is requested at
dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.ViewModelFactoriesEntryPoint.getHiltViewModelMap() [com.rober.fileshortcut_whereismyfile.App_HiltComponents.SingletonC ? com.rober.fileshortcut_whereismyfile.App_HiltComponents.ActivityRetainedC ? com.rober.fileshortcut_whereismyfile.App_HiltComponents.ViewModelC]
Here's the code (I don't think there's anything wrong here)
#Module
#InstallIn(ActivityComponent::class)
object PermissionModule {
#ExperimentalCoroutinesApi
#Provides
#ActivityScoped
fun providePermissionsHelper(
#ActivityContext context: Context,
) = PermissionsUtils(context)
}
#HiltViewModel
#ExperimentalCoroutinesApi
class FileViewModel #Inject constructor(
private val class1: Class1,
private val class2: Class2,
private val class3: Class3,
private val permissionsUtils: PermissionsUtils //Here's the one I'm injecting
)
My guesses: It's telling me that I can't inject in ActivityComponent because FileViewModel is injected in ActivityRetainedComponent/SingletonComponent/ViewModelComponent, because other dependencies provided to viewmodel are installed in this component?
Question: What is really going here? What am I missing and is there any solution for using ActivityComponent? I really need the #ActivityContext for that class!
Edit: This works when I change to #InstallIn(ActivityRetainedComponent::class) and the context to #ApplicationContext context: Context, note that it works with SingletonComponent/ViewModelComponent/ActivityRetainedComponent, which makes sense. But still I don't know how to make it work the #ActivityComponent
Edit 2: I've come to the conclusion that is not possible since you are installing in ViewModel and the components that work with ViewModel are Singleton/ActivityRetained/ViewModel, so there's no way to inject ActivityContext in a ViewModel since it requires the component #ActivityComponent and this is 1 level below of ViewModelComponent.
Source: https://dagger.dev/hilt/components#fn:1
so there's no way to inject ActivityContext in a ViewModel since it requires the component #ActivityComponent and this is 1 level below of ViewModelComponent.
You got it.
Think of Dagger/Hilt components (and scopes) in terms of lifecycle. In Android, a ViewModel has longer lifecycle than its containing Activity or Fragment. That's the whole point of the class, it's just the name that's unfortunate. It may help to think of those as RetainedInstance or AnObjectThatSurvivesActivityConfigurationChanges. Yeah, these aren't as catchy as ViewModel.
If you injected a short-lived object (the Activity) into a long-lived one (the ViewModel), the latter would keep a reference to the former. That's a memory leak - the Activity and everything it contains (e.g. views, bitmaps) stays in memory even though it's no longer used. A couple of those and you risk an OutOfMemoryError.
However, the other way around - i.e. injecting a long-lived object into a short-lived one - is safe. That's why you got it working with Singleton/ActivityRetained/ViewModel components.
Hilt's component hierarchy makes it harder for you to create memory leaks. And all of that happens at compile-time, which gives you faster feedback as opposed to runtime-based solutions. Much better than your users finding out by crashing!
I have a class other than an Activity with injected fields :
public Class1 extends Class2 {
#Inject A a;
}
public Class2 extends Class3 {
#Inject B b;
}
public Class3 {
#Inject C c;
}
And I am providing Class1 like this:
#Provides
Class1 provideClass1(){
return new Class1();
}
My question : when providing Class1 will Dagger inject B and C at the level of the super class? Or should I use constructor injection and call super inside the constructors?
Activity, Fragment, and Service are ideal sites for property injection because you have no control over their constructor and their instantiation is handled by the Android OS.
For the rest of your classes, constructor injection gives much more control and makes it easier to unit test. When writing a unit test, you simply pass in the test doubles you want when you are initialising the system under test. You also make it easier to write immutable classes fulfilling Effective Java item 15.
As for your other question, Dagger 2 is smart enough to inject properties in a super class. In other words, if you have a ConcreteService extends BaseService and you call component.inject(this) inside ConcreteService Dagger 2 will know to inject the properties of BaseService if they are annotated correctly.
However, in your example you have a mix of property injection (the #Inject annotations inside Class1 and Class2) and constructor injector (the #Provides method for Class1 which simply uses the constructor). This is not enough to have all the properties in the super classes of Class1 to be injected because you have specified the medium of provision of Class1 to be a simple call to the constructor. On the other hand, if you obtained the instance of Class1 through a component (through provision methods) or through requesting injection on a class which has a #Inject annotated Class1 property then you would get a fully-formed instance.
Say class A depends on B, and that I want to test class A.
I create a test for class A in which I want to mock B.
Class B is injected into class A using Dagger2 (using a Module and a Component). Meaning, class A has a class member:
#Inject
B mB;
In my test class, I create an instance of A in the setUp() method.
How to I provide the mocked instance of the class B to A?
You will need to provide a mock implementation using a mock build flavor if you normally inject your dependency with Dagger. Usually this is done by replacing something like ProdModule with MockModule and then #Provides a mock implementation instead in your mock or test flavor.
Otherwise, a good testing option is Mockito if you want to guarantee that your mock returns what you want it to so that you don't have to worry about the mock implementation having a bug in it.
I have some misunderstandings of the way that dagger works:
There are only two ways to satisfy dependency: whether the #Provide method returns the instance, or the class should have #Singleton annotation, is that right? Must the class constructor have #Inject annotation in the latter case?
As I see, the ObjectGraph generates all the injection stuff. And it's said, that its inject(T instance) should be called to inject fields. However, I can just annotate my field with #Inject and it goes (field's class is #Singletone). ObjectGraph is not needed to satisfy such a dependency, right?
What about injects{} in #Module, what specifically does it give? Plz, provide an example of benefit when you keep all the injectable classes list.
Yes, there are two ways: #Provide methods in modules and #Singleton classes with #Inject at constructor.
Must the class constructor have #Inject annotation in the latter case?
Yes, otherwise object wouldn't be created by Dagger.
I don't think #Singleton with field injection could work. Because #Singleton at class with constructor injection means Dagger is responsible to keep one instance of this class. And it can create this class using constructor injection if all dependencies are satisfied. However, #Singleton with field injection seems misuse to me, cause keeping single instance of this class is user's responsibility now. Dagger can't instantiate this object itself.
Are you sure this configuration compiles and runs? And if it is check #Inject fields, they should be null by my understanding.
injects={} in #Module returns set of classes passed to ObjectGraph.inject(T class) or ObjectGraph.get(Class<T> class). Referring documentation:
It is an error to call ObjectGraph.get(java.lang.Class) or ObjectGraph.inject(T) with a type that isn't listed in the injects set for any of the object graph's modules. Making such a call will trigger an IllegalArgumentException at runtime.
This set helps Dagger to perform static analysis to detects errors and unsatisfied dependencies.
You can find some examples in this thread.