Dagger Module Inclusion Cycle - android

I'm still new to Dagger and trying to get a hang of things. I wanted to split my modules into logical groups which each provide their own functionality, yet basically would act the same as if it were in one Module.
For instance, let's say I have my main application module defined as follows:
//com.example.android.MyAppModule.java
#Module(
includes = AnalyticsModule.class,
injects = { <snip> }
)
public class MyAppModule {
// various provides
}
And I have another module defined like this which sets up an ErrorReporter interface and provides the concrete implementation to it.
// com.example.android.analytics.AnalyticsModule.java
#Module(
addsTo = MyAppModule.class,
injects = { MyApp.class }
)
public class AnalyticsModule(){
// ErrorReporter is a public interface and ErrorReporterImpl is a package-local final concrete class that implements it
#Provides #Singleton
ErrorReporter providesErrorReporter(ErrorReporterImpl reporter) { return reporter };
}
In my Application class I set up the object graph like this:
// com.example.android.MyApp.java
public class MyApp extends Application {
#Inject ErrorReporter errorReporter;
#Override
public void onCreate() {
super.onCreate();
applicationGraph = ObjectGraph
.create(new MyAppModule())
.plus(new AnalyticsModule());
applicationGraph.inject(this);
errorReporter.initialize();
}
}
When I run the dagger compiler I get something like this:
Graph validation failed: Module Inclusion Cycle:
0. com.example.android.analytics.AnalyticsModule included by com.example.android.MyAppModule
1. com.example.android.modules.MyAppModule included by com.example.android.analytics.AnalyticsModule
0. com.example.android.analytics.AnalyticsModule
What am I doing wrong here? I assume it has something to do with includes/addsTo, but when I remove those I get other errors.
If I remove includes = AnalyticsModule.class from MyAppModule I get something like this:
com.example.android.analytics.ErrorReporter could not be bound with key com.example.android.analytics.ErrorReporter required by com.example.android.MyApp for com.example.android.MyAppModule
Everything is fine if I completely forgo an AnalyticsModule and then hand off the providesErrorReporter to MyAppModule, but then I have to make my concrete impl class public so I can use it in the other module.

#Module(includes = AnalyticsModule.class) is useful for composing multiple modules into one module. In this case, using .plus(new AnalyticsModule()) is wrong because AnalyticsModule has already been included by the includes line in the annotation. So you should remove the call to plus().
#Module(addsTo = MyAppModule.class) is for allowing a call to .plus(new AnalyticsModule()). Since we've removed that call, we should remove the addsTo as well.
#Module(complete = false) is necessary for AnalyticsModule because some of its dependencies can't be satisfied on its own. This is okay; as long as MyAppModule has complete = true (the default), Dagger will do the necessary error checking.
Admittedly, the "Module Inclusion Cycle" error was a little bit unclear. The cycle was caused by A including B which addsTo A.

addsTo= is there to specify that this graph is an extension to the referenced module in a parent/child graph. .plus() is the run-time version of this. You only need .plus() if you have shorter-lived graph-managed instances. This corresponds roughly to the notion of "scope" in Guice and other DI containers.
In your example you are doing:
applicationGraph = ObjectGraph
.create(new MyAppModule())
.plus(new AnalyticsModule());
This ends up creating two graphs in a parent/child relationship, which isn't what you need here. IN an android app you want one graph for the Application.
You can simply remove addsTo and MyAppModule will automatically instantiate AnalyticsModule, or you can pass both in if you want to avoid the dynamic initialization like so:
applicationGraph = ObjectGraph.create(new MyAppModule(), new AnalyticsModule());
The module inclusion cycle is because you have a cyclical relationship between these two modules and modules must themselves form an acyclic graph of configuration. MyAppModule includes AnalyticsModule which in turn includes MyAppModule. (addsTo is a less rigorous includes= used to specify things obtained from parent graphs)

Related

How inject dagger non empty constructor module dependencies to hilt

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.

Android Dagger and setting a module at arbitrary moment

I am a newbie in using Dagger and DI. I am trying to use AndroidInjection resolver for injecting dependencies into fragments of its activity.
Generally, I understood that, in the case of using Dagger.android, I have to create MyAppComponent and install AndroidInjectionModule in order to use AndroidInjection.inject(Activity/Fragment/etc..). In this way, I have provided Subcomponents' interfaces with Builders to make Dagger able to generate appropriate injectors.
But what if I have Subcomponent, i.e. DeviceFragmentSubcomponent that has a dependency on the module with parameterized constructor?
#Subcomponent(modules = {DeviceModule.class})
public interface DevicePageFragmentSubcomponent extends AndroidInjector<DevicePageFragment>{
#Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder<DevicePageFragment>{
public abstract Builder setDeviceModule(DeviceModule deviceModule);
}
}
#Module
public class DeviceModule {
private Device mDevice;
public DeviceModule(Device device) {
mDevice = device;
}
#Provides
public Device provideDevice(){
return mDevice;
}
}
What should be done to set DeviceModule instance within DeviceActivity for using AndroidInjection.inject(this) in its fragments?
Is it possible to add required modules not at the moment of creation application's dependency tree, but on the arbitrary event?
The Android Injection part of Dagger can (currently) only be used along with AndroidInjection.inject(this), where it will inject the given Android Framework type with a predefined module.
As such, there is no way to pass in a parameter or module.
Your first option would be not to use the Android Injection part of Dagger. Just create your component as you see fit and inject your object.
The second option would be to not use a parameter / module. In theory, if your Activity can create a DeviceModule, so can Dagger, given that it has access to the Activity—and by using the Android Injection parts, the component injecting your type has access to it.
You did not specify what dependency Device has or why you need to pass it to the DeviceModule from your fragment.
Let's say your Device depends on DevicePageFragment.
class Device {
#Inject Device(DevicePageFragment fragment) { /**/ } // inject the fragment directly
}
You can access the fragment and do what you would do. If that's not your case, let's say you need to read the arguments Bundle. You could modify your Module to not take a device, but rather to create it iself, and getting rid of the constructor argument as well.
#Module
public class DeviceModule {
// no constructor, we create the object below
// again we take the fragment as dependency, so we have full access
#Provides
public Device provideDevice(DevicePageFragment fragment){
// read your configuration from the fragment, w/e
long id = fragment.getArguments().getLong("id")
// create the device in the module
return new Device(id);
}
}
In the end it really depends on your usecase.
What I tried to show is that you have access to the object that you are trying to inject. This means that whatever you can do within this object, you can do within Dagger. There is no need for parameterized modules, since you can extract those parameters from the target, as seen above.

Dagger 2 injection in Fragments and swapping Modules

I've started using Dagger 2 and I'm not sure if I'm doing things right as I ran in the following complication:
Let's say I have a HouseModule initialized with a House and a WindowModule initialized with a Window.
Now I have a HouseFragment which is supposed to general Information about the house.
Thus I created a HouseComponent including the HouseModule.
So far so good.
Now there are multiple nested HouseDetailsFragments within the HouseFragment(ViewPager) which show information about the House and the Window.
I created a HouseDetailsComponent including the HouseModule and the WindowModule.
My dependency graph looks like this:
// provides application wide dependencies (Application context, SharedPref, Repository,...)
#Component(modules = {ApplicationModule.class})
public interface ApplicationComponent {
}
// provides general activity dependencies (Navigator,... )
#Component(dependencies = ApplicationComponent, modules = {ActivityComponent.class})
public interface ActivityComponent{
void inject(MainActivity mainActivity);
}
// provides house fragment specific dependencies (HousePresenter,...)
#Component(dependencies = ApplicationComponent, modules = {ActivityComponent.class, HouseComponent.class})
public interface HouseComponent extends ActivityComponent {
void inject(HouseFragment fragment);
}
// provides house details fragment specific dependencies (HouseDetailsPresenter,...)
#Component(dependencies = ApplicationComponent, modules = {ActivityComponent.class, HouseComponent.class, WindowComponent.class})
public interface HouseDetailsComponent extends ActivityComponent {
void inject(HouseDetailsFragment detailsFragment);
}
Component creation becomes increasingly complex this way and I wonder how to inject dependencies to the HouseFragment and especially the HouseDetailsFragments best.
e.g. to build the HouseDetailsComponent I have to do the following:
// casting omitted
DaggerHouseDetailsComponent.builder()
.applicationComponent(getActivity().getApplication().getApplicationComponent())
.activityModule(getActivity().getActivityModule())
.houseModule(getParentFragment().getHouseModule())
.windowModule(new WindowModule(window)).build().inject(this);
I dislike the knowledge (and casting) required about parent fragments and activities.
Is there a simpler way to achieve what I want to do? Any other suggestions about the dependency graph?
Also how do I show a different House in the HouseFragment? i figured creating a new HouseModule and then swapping the other out... but how do I do that? There are no accessors afaik.
I've been using submodules to simplify the hierarchy.
Also Dagger 2.11+ supports Android and makes injections for it easier.

How to inject same Dagger 2 dependency with different implementations based on the Context?

I have a Dagger 2 dependency that is provided differently if it’s an Activity or some other context instance (e.g., a Service). Both would refer to the same interface but their implementation varies. How could I organise this using Dagger 2?
Right now, I’m trying with two different components, ActivityComponent and ContextComponent with their respective modules as follows:
#ActivityScope
#Subcomponent(
modules = {
ActivityModule.class,
ContextModule.class
})
public interface ActivityComponent {
}
#Module
public class ActivityModule {
#Provides
#MyActivityQualifier
public MyObject provideMyObject() {
}
}
#ContextScope
#Subcomponent(
modules = {
ContextModule.class
})
public interface ContextComponent { }
#Module
public class ContextModule.class {
#Provides
public MyObject provideMyObject() {
}
}
Then, if I’m using MyObject in an Activity, I have to add the qualifier as follows:
#Inject #MyActivityQualifier MyObject myObject;
This feels wrong but I’m not sure why. Is there a better way?
You are using subcomponents.
The question is: Can or should both objects be "visible" (usable, injectable, ...) at the same time?
Yes: You will have to use some sort of qualifier. Or else you can't distinguish them.
No: You can "hide" them by not exposing the dependency from the dependent component. You would have to use a normal component instead of #Subcomponent in this case, and just don't add the getMyInterface() method to your parent component.
And there aren't any other options, because you have 2 components dependent on each other. So your approach looks fine with the information you provide.
Note you can also just qualify one of your implementations. In my project, I use a qualified #Named("public") annotation for a common implementation without user data. If I don't add any qualifier I will just get the other one (unqualified).
With independent components, e.g. ActivityAComponent and ActivityBComponent you could just switch which implementation gets provided using different modules.

How to migrate missing inject from module with complete = false from Dagger 1 to Dagger 2

I have a library project/module that is used by both Android apps and regular java apps.
In Dagger 1 this project/module has property complete = false. Within there is an #Inject field that is not satisfied by any class implementation or #Provides method. The idea is to force the "top" module(s) which has complete = true to provide system specific implementation
Just for the sake of example: In the library project I have ActLogin activity that have field #Inject #Named("app version") mAppVersion. The value of this field is used when logging in into a server. ActLogin is used by several apps that use this library. Each app's module has complete = true and provides value with #Provides #Named("app version") provideAppVersion()
Documentation for migration of Dagger 2 (http://google.github.io/dagger/dagger-1-migration.html) states:
Dagger 2 modules are all declared as complete = false and library = true
and in the same time the "main" documentation page (http://google.github.io/dagger/) states:
The Dagger annotation processor is strict and will cause a compiler error if any bindings are invalid or incomplete.
The latter is obviously the correct one because when trying to build with unsatisfied inject error is produced (error: java.lang.String cannot be provided without an #Provides- or #Produces-annotated method).
The question is: is it possible to migrate this approach (deferring providing inject) to Dagger 2 and how?
P.S. Initially I thought as a dirty workaround to provide some dummy values in the library's #Module but then again - you cannot have module overrides in Dagger 2 (which is kind of WTF(!!!). Module overrides were the most useful feature for me when creating unit tests). Probably I am missing something very basic and I hope that someone can point it out :-).
It turns out that there is dedicated construct for this but it takes some time to find it out.
If you need to have a component that have a module which contains unsatisfied inject(s) - make it #Subcomponent. As documentation clearly states:
That relationship allows the subcomponent implementation to inherit the entire binding graph from its parent when it is declared. For that reason, a subcomponent isn't evaluated for completeness until it is associated with a parent
So in my case, my library project needs to be a dagger subcomponent. When I use it in my app project, my app dagger component have to include the lib subcomponent.
In code:
The library subcomponent:
#Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
void inject(Mod1Interface1 in);
}
The app component:
#Component(modules = Mod2.class)
#Singleton
public interface MyAppComponent {
void inject(MainActivity act);
MyLibraryComponent newMyLibraryComponent();
}
Please note the MyLibraryComponent newMyLibraryComponent(); - that is how you tell dagger that your component contains that subcomponent.
Graph instantiation:
MyAppComponent comp = DaggerMyAppComponent.builder().build();
Please note that contrary to using component composition with dependencies (#Component's property) in this case you don't have to "manually" construct your subcomponent. The component will "automatically" take care for that in case the subcomponent's modules don't need special configuration (i.e. constructor parameters). In case some subcomponent's module requires configuration you do it trough the component instantiation like this:
MyAppComponent comp = DaggerMyAppComponent.builder().
mod2(new Mod2SpecialConfiguration()).
build();
For android there is a special twist if your library project contains activities because each activity have to be separately injected "on demand" contrary to regular java application where you usually inject the whole application once at start up time.
For the sake of example let's say our library project contains login activity "ActLogin" that we use as common for several applications.
#Subcomponent(modules = Mod1.class)
public interface MyLibraryComponent {
void injectActLogin(ActLogin act);
void inject(Mod1Interface1 in);
}
The problem is that in Android we usually create our dependency graph in the Application object like this:
public class MyApplication extends Application {
private MyAppComponent mAppDependencyInjector;
#Override
public void onCreate() {
super.onCreate();
mAppDependencyInjector = DaggerMyAppComponent.builder().build();
}
public MyAppComponent getAppDependencyInjector() {
return mAppDependencyInjector;
}
}
and then in your activity you use it like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
((MyApplication) getApplication()).getAppDependencyInjector().inject(this);
// ...
}
but our ActLogin activity is part of the library project (and dagger component) that is not event aware of what application will it be used in so how are we going to inject it?
There is a nice solution but please note that I am not sure it is canonical (i.e. it is not mentioned in the documentation, it is not given as an example by the "authorities" (afaik))
Project's source can be found at github.
First you will have to extend the library dagger component in you app component:
public interface MyAppComponent extends MyLibraryComponent {
That way your app component will contain all the inject methods from the subcomponent so you will be able to inject it's activities too. After all, top component is in fact the whole object graph (more precisely the Dagger generated DaggerMyAppComponent represent the whole graph) so it is able to inject everything defined in itself + in all subcomponents.
Now we have to assure that the library project is able to access it. We create a helper class:
public class MyLibDependencyInjectionHelper {
public static MyLibraryComponent getMyLibraryComponent(Application app) {
if (app instanceof MyLibraryComponentProvider) {
return ((MyLibraryComponentProvider) app).getMyLibraryComponent();
} else {
throw new IllegalStateException("The Application is not implementing MyLibDependencyInjectionHelper.MyLibraryComponentProvider");
}
}
public interface MyLibraryComponentProvider {
MyLibraryComponent getMyLibraryComponent();
}
}
then we have to implement MyLibraryComponentProvider in our Application class:
public class MyApplication extends Application implements
MyLibDependencyInjectionHelper.MyLibraryComponentProvider {
// ...
#Override
public MyLibraryComponent getMyLibraryComponent() {
return (MyLibraryComponent) mAppDependencyInjector;
}
}
and in ActLogin we inject:
public class ActLogin extends Activity {
#Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
// ...
MyLibDependencyInjectionHelper.getMyLibraryComponent(getApplication()).
injectActLogin(this);
// ...
}
}
There is a problem with this solution: If you forget to implement the MyLibraryComponentProvider in your application you will not get an error at compile time but at runtime when you start ActLogin activity. Luckily that can be easily avoided with simple unit test.

Categories

Resources