I am using Dagger2 and I want to know if it is possible to use the new Android Injector for dependent components? I have seen a few tutorials that use subcomponents and the base App component will just inject everything.
AppComponent
#Singleton
#Component(modules = [AndroidSupportInjectionModule::class])
interface ApplicationComponent {
fun inject(app: App)
#Component.Builder
interface Builder {
fun build(): ApplicationComponent
#BindsInstance
fun app(app: Context): Builder
}
}
QueueComponent
#QueueScope
#Component(dependencies = arrayOf(ApplicationComponent::class), modules = [ScreenBindingModule::class])
interface QueueComponent {
}
ScreenBindingModule
#Module
abstract class ScreenBindingModule {
#ContributesAndroidInjector()
abstract fun queueActivity(): QueueActivity
}
In the onCreate I have added AndroidInjection.inject(this) but the problem is that the app crashes with the exception:
Caused by: java.lang.IllegalArgumentException: No injector factory bound for Class....
No, this will not work without more configuration. As in AndroidInjection.inject:
Application application = activity.getApplication();
if (!(application instanceof HasActivityInjector)) { /* ... */ }
Given an Activity there's no easy way to determine which wider-than-Activity-scoped object should be used to create the Activity's subcomponent, so dagger.android tries to cast the Application to the type HasActivityInjector. Your Application evidently exposes HasActivityInjector to get that error message—likely by marking DaggerApplication or your custom subclass of DaggerApplication in your manifest—but that implementation just returns the DispatchingAndroidInjector<Activity> that searches a multibindings map.
Though #ContributesAndroidInjector automatically installs into that map, each of your Components (ApplicationComponent and QueueComponent) contains a different map, and your DaggerApplication only consults the one in ApplicationComponent.
In order to make this work with component dependencies, you would need to have your Application subclass's activityInjector method return a different AndroidInjector implementation, which creates/fetches a QueueComponent instance and reads the multibound Maps (which would necessarily be exposed on QueueComponent). Unfortunately, there isn't really a way to bind multiple elements into your ApplicationComponent's existing multibound map, as tracked in https://github.com/google/dagger/issues/687, so making this work would involve a custom AndroidInjector implementation at least.
#ContributesAndroidInjector()
abstract fun queueActivity(): QueueActivity
Which module contains this part of code? In QueueComponent You are added ScreenBindingModule::class, but define injector factory in another class - QueeScrennBindingModule::classs.
It's just a typo or it's really two different classes?
Related
I have the following module that is used in the data layer of my application which is a plain Android Library.
#Module
interface MapperModule {
#Binds
fun bindDomainToDataMapper(domainToDataMapperImp: DomainToDataMapperImp)
: DomainToDataMapper<TodoTaskEntity, ToDoTaskModel>
#Binds
fun bindDataToDomainMapper(dataToDomainMapperImp: DataToDomainMapperImp)
: DataToDomainMapper<ToDoTaskModel, TodoTaskEntity>
}
I am just wondering what the #InstallIn scope should be as this is the Data Layer so is not specific to any android components.
I was thinking of using #InstallIn(SingleComponent::class) but I don't want these classes to be singleton.
Any ideas of what this should be?
Hilt has predefined components for Android that are managed for you. However, there may be situations where the standard Hilt components do not match the object lifetimes or needs of a particular feature
Custom component limitations
Custom component definitions currently have some limitations:
Components must be a direct or indirect child of the
SingletonComponent. Components may not be inserted between any of the
standard components. For example, a component cannot be added between
the ActivityComponent and the FragmentComponent.
To create a custom Hilt component, create a class annotated with #DefineComponent. This will be the class used in #InstallIn annotations.
The parent of your component should be defined in the value of the #DefineComponent annotation. Your #DefineComponent class can also be annotated with a scope annotation to allow scoping objects to this component.
#DefineComponent(parent = SingletonComponent::class)
interface MyCustomComponent
A builder interface must also be defined. If this builder is missing, the component will not be generated since there will be no way to construct the component. This interface will be injectable from the parent component and will be the interface for creating new instances of your component. As these are custom components, once instances are built, it will be your job to hold on to or release component instances at the appropriate time.
Builder interfaces are defined by marking an interface with #DefineComponent.Builder. Builders must have a method that returns the #DefineComponent type. They may also have additional methods (like #BindsInstance methods) that a normal Dagger component builder may have.
#DefineComponent.Builder
interface MyCustomComponentBuilder {
fun fooSeedData(#BindsInstance foo: Foo): MyCustomComponentBuilder
fun build(): MyCustomComponent
}
While the #DefineComponent.Builder class can be nested within the #DefineComponent, it is usually better as a separate class. It may be separated into a different class as long as it is a transitive dependency of the #HiltAndroidApp application or #HiltAndroidTest test. Since the #DefineComponent class is referenced in many places via #InstallIn, it may be better to separate the builder so that dependencies in the builder do not become transitive dependencies of every module installed in the component.
For the same reason of avoiding excessive dependencies, methods are not allowed on the #DefineComponent interface. Instead, Dagger objects should be accessed via entry points.
#EntryPoint
#InstallIn(MyCustomComponent::class)
interface MyCustomEntryPoint {
fun getBar(): Bar
}
class CustomComponentManager #Inject constructor(
componentBuilder: MyCustomComponentBuilder) {
fun doSomething(foo: Foo) {
val component = componentBuilder.fooSeedData(foo).build();
val bar = EntryPoints.get(component, MyCustomEntryPoint::class.java).getBar()
// Don't forget to hold on to the component instance if you need to!
}
Conclusion:
Even if you create a custom component indirectly, it will look like a Singleton.
Even if you use #InstallIn(SingleComponent::class) without #Singleton annotation these object won' t be singleton. They will be non-scoped objects, and for every request, you will have new instance for these classes.
I was thinking of using #InstallIn(SingleComponent::class) but I don't want these classes to be singleton.
It means you can use #InstallIn(SingleComponent::class) without #Singleton annotation.
I started to migrate Dagger application to Hilt, first I'm converting AppComponent to Hilt auto-generated ApplicationComponent. Therefore I've added #InstallIn(ApplicationComponent::class) annotation to each module related to this component.
Now I get the following error:
error: [Hilt] All modules must be static and use static provision
methods or have a visible, no-arg constructor.
It points to this module:
#InstallIn(ApplicationComponent::class)
#Module
class AccountModule(private val versionName: String) {
#Provides
#Singleton
fun provideComparableVersion(): ComparableVersion {
return ComparableVersion(versionName)
}
}
Previously in Dagger, it was possible to pass arguments in the constructor. Looks like Hilt doesn't allow this.
How can I pass arguments to Hilt module?
#InstallIn(ApplicationComponent::class)
#Module
class AccountModule {
#Provides
#Singleton
fun provideComparableVersion(application: Application): ComparableVersion {
return ComparableVersion((application as MyApplication).versionName)
}
}
If you don't want to see MyApplication, then you can use an interface.
Unfortunately for now Dagger Hilt is design using monolithic component, where there's only one Application Component and one Activity Component auto generated by it. Refer to https://dagger.dev/hilt/monolithic.html
Hence the modules for it must be static and use static provision methods or have a visible, no-arg constructor.
If you put an argument to the module, it will error out stating
[Hilt] All modules must be static and use static provision methods or have a visible, no-arg constructor.
From my understanding, you'll trying to get the BuildInfo version number, perhaps the easiest way is to use the provided BuildInfo.VERSION_NAME as below.
#InstallIn(ApplicationComponent::class)
#Module
class AccountModule() {
#Provides
#Singleton
fun provideComparableVersion(): ComparableVersion {
return ComparableVersion(BuildInfo.VERSION_NAME)
}
}
If you like to set it yourselves instead of relying on BuildInfo.VERSION_NAME, you can define static const variable that exist differently across flavour.
In my Android project, I have two project modules, an main module and a core module.
In main module, I have a dagger component, MainComponent:
// it has dependency on CoreComponent
#Component(modules = [MyModule::class], dependencies = [CoreComponent::class])
#FeatureScope
interface MainComponent {
fun inject(mainActivity: MainActivity)
}
As you can see above, MainComponent has a dependency on CoreComponent. It also has a custom scope annotation #FeatureScope.
In core module I have another dagger component called CoreComponent:
#Component(modules = [CoreModule::class])
#Singleton
interface CoreComponent {
fun getExpensiveObject(): ExpensiveObject
}
#Module
class CoreModule {
#Provides
#Singleton
fun provideExpObj(): ExpensiveObject = ExpensiveObject()
}
The CoreComponent is annotated by Dagger defined #Singleton scope.
I build the Main component in onCreate() of Application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
//build main component along with core component
mainComponent = DaggerMainComponent
.builder()
.myModule(MyModule())
.coreComponent(DaggerCoreComponent.builder().build())
.build()
}
}
CoreComponent & its providers are annotated by #Singleton, while MainComponent & its providers are annotated by custom annotation #FeatureScope.
Question one: From lifetime perspective, does the code mean the lifetime of objects in MainComponent is shorter than that in CoreComponent due to the scope annotations (#Singleton in CoreComponent and #FeatureScope in MainComponent)?
Question two: Since the components are built in Application class onCreate() which is the entry point of app at runtime, am I right that even though components in two project modules are annotated by different scope annotation, their objects basically have the same lifetime as the whole app's at runtime?
(I ask those questions because my understanding is that the Dagger defined #Singleton scope has the longest lifetime, but I get confused by that with my project)
Yes, the fact that CoreComponent is annotated with #Singleton and the component instance is created in the Application means that there will be a single ExpensiveObject created in the lifetime of the application.
Concerning the custom annotation (#FeatureScope)
The component implementation ensures that there is only one provision of each scoped binding per instance of the component.
ref
But since, the MainComponent is created only once per application, this custom annotation is effectively the same as the Singleton annotation.
If you want a feature-scoped object then you should remove it from the MainComponent and have this annotation only on a sub-component. Read the dagger tutorial, and in particular the step 13.
Annotating both WithdrawalLimiter and UserCommandsRouter with
#PerSession indicates to Dagger that a single WithdrawalLimiter should
be created for every instance of UserCommandsRouter.
#PerSession
final class WithdrawalLimiter { ... }
#PerSession
#Subcomponent(...)
interface UserCommandsRouter { ... }
I'm having trouble injecting a fragment that I contribute via #ContributesAndroidInjector.
I'm trying to build a hierarchy within modules (features). Basically, what I have is:
Core
App
Feature
My AppComponent depends on CoreComponent:
#Singleton
#Component(modules = [CoreModule::class])
interface CoreComponent {
fun getApp(): Application
#Component.Factory
interface Factory {
fun create(#BindsInstance app: Application): CoreComponent
}
}
And initialize as:
#AppScope
#Component(
modules = [
AndroidInjectionModule::class,
ActivityContributor::class,
AppModule::class],
dependencies = [CoreComponent::class]
)
interface AppComponent : AndroidInjector<App> {
#Component.Factory
interface Factory {
fun create(component: CoreComponent): AppComponent
}
}
This part is pretty much straightforward. AppComponent has ActivityContributor which only has one #ContributesAndroidInjector, which is MainActivity.
Now, problem starts when I want to add an encapsulated feature subcomponent. Assume I have two fragments FragmentOne and FragmentTwo in my feature, with some common dependencies as well as their own.
What I want to have is a FeatureSubcomponent which has FeatureModule and FeatureContributor:
#FeatureScope
#Subcomponent(
modules = [FeatureContributor::class,
FeatureModule::class
]
)
abstract class FeatureSubcomponent {
#Subcomponent.Factory
interface Factory {
fun create(): FeatureSubcomponent
}
}
While FeatureModule has dependencies common for both fragments, FeatureContributor is contributing FragmentOne and FragmentTwo with their own modules:
#Module
abstract class FeatureContributor {
#ContributesAndroidInjector(modules = [FeatureOneModule::class])
abstract fun featureOneFragment(): FeatureOneFragment
#ContributesAndroidInjector(modules = [FeatureTwoModule::class])
abstract fun featureTwoFragment(): FeatureTwoFragment
}
And of course, I add FeatureSubcomponent as a subcomponent to AppModule:
#Module(subcomponents = [FeatureSubcomponent::class])
And if you scroll up, you'll see AppModule is included in modules of AppComponent.
Problem is, while it's compiling and running, it crashes once it reaches to any feature fragments due to No injector factory bound for error.
Roughly summarising my structure:
CoreComponent
AppComponent
FeatureComponent (SUB)
FeatureOneComponent (SUB)
FeatureTwoComponent (SUB)
Anyone has ideas about why or how it should be instead or am I missing something ?
Edit
Here's the diagram I prepared to make it easier to understand hierarchy
Dagger android does injection by finding the closest injector in the current scope. For Fragment, it is the containing Activity and for Activity it is the Application instance.
When you call AndriodInject.inject(this) in the Fragment it is goes up the hierarchy to find injector and then injects the Fragment.
Have you extended DaggerAppCompatActivity/implemented the HasFragmentInjector?
Another thing I see is, why create subcomponent again when #ContributesAndroidInjector already creates a sub component internally for you?
#ContributesAndroidInjector.modules is a way for talking to the generated subcomponent. So the relationship between Activity and FeatureFragment must be established in the subcomponent that the #ContributesAndroidInjector will be generating.
Right now your hierarchy is like this since you have deliberately specified FeatureSubComponent to be subcomponent of the AppComponent
App -> FeatureSubComponent -> [A, B] (generated)
\
\---> MainActivitySubcomponent (generated by contributesAndroidInjector).
What you actually need is:
App -> MainActivitySubComponent (generated) -> [Feature A , Feature B] (generated)
To do this
#ContributesAndroidInjector(modules = [FeatureContributor::class])
abstact fun mainActivity() : MainActivity
Feature contributor can have #ContributesAndroidInjectors inside. When Dagger compiler sees that FeatureContributor has #ContributesAndroidInjectors, it makes the subcomponent generated by those to be the subcomponent of the activity since it is the parent.
Which basically means Activity -> Fragment hierarchy.
Hi I have two modules seperated in domain packages in my project and have firebase insance service class that injects itself manually using DaggerAppComponent.
Then I have two inject dependencies located on the two modules I mentioned.
ModuleOne has the dependency called storage and ModuleTwo has one called delegator.
When atempting to inject both of these inside my firebase service class, it complains that it can't locate and find the delegator injector from ModuleTwo.
However, if I copy the provides method interface of the delegator into ModuleOne it sort of works(now complains that its bound multiple times but can easily fix that by adding naming convention).
This is a hacky way of doing it which I am not keen to do and just want the ability to use any dependencies from different modules. Is this possible?
Below is my firebase service class
class MyInstanceIdService : FirebaseInstanceIdService() {
#Inject
lateinit var delegator: AccountDelegatorContract
#Inject
lateinit var Storage: StorageContract
override fun onCreate() {
super.onCreate()
AndroidInjection.inject(this)
}
override fun onTokenRefresh() {
DaggerApplicationComponent.builder().application(application as MyApplication).build().inject(this)
val refreshToken = FirebaseInstanceId.getInstance().token
// val pianoStorage: PianoStorage = PianoStorage(this)
sendTokenToServer(refreshToken, storage.getUniqueId())
}
private fun sendTokenToServer(refreshToken: String?, uniqueId: String) {
//TODO: Send this new token to server
delegator.sendPushToken(PushTokenRequest(refreshToken!!, uniqueId))
}
Here is moduleOne which represents the main Module that houses dependencies that are used in multiple domain packages in my application.
#Module
abstract class MainModule {
#ContributesAndroidInjector(modules = [AccountModule::class])
#ViewScope
abstract fun bindInstanceIdService(): MyInstanceIdService
#Provides
#Singleton
#JvmStatic
fun provideApplicationContext(application: Application): Context = application
#Provides
#Singleton
#JvmStatic
fun provideStorageData(storage: MainStorage): StorageContract = storage
}
Here is ModuleTwo that is specific to a domain packages
#Module
abstract class AccountModule {
#Module
companion object {
#ViewScope
#JvmStatic
#Provides
fun providesAccountDelegator(accountDelegate: AccountDelegator): AccountDelegatorContract = accountDelegate
#ViewScope
#JvmStatic
#Provides
fun providesAccountControllerContract(accountNetworkController: AccountNetworkController): AccountControllerContract = accountNetworkController
}
}
My app is organised in different packages that represent a part/domain of the app such as accounts, users, vehicle, message etc etc and with each domain has its own module which defines specific dependencies related to that domain.
My issue is that how can I use dependencies above located on different modules?
Edit: my appCOmponent looks like this
#Singleton
#Component(modules = arrayOf(MainModule::class,
AccountModule ::class))
interface ApplicationComponent : AndroidInjector<DaggerApplication> {
fun inject(MyApplication: MyApplication)
fun inject(myInsanceIdService: MyInstanceIdService)
override fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(applicaton: Application): Builder
fun build(): ApplicationComponent
}
}
You add you AccountModule (with provision methods of ViewScope sope) to your AppComponent as well as your subcomponent (declared with #ContributesAndroidInjector bindInstanceIdService), so I wonder how/why this compiles at all. So first of all you should remove the AccountModule from your app component, since it has a different scope anyways and won't do you any good there.
Secondly, you call DaggerApplicationComponent.inject(this) in your MyInstanceIdService, although ApplicationComponent does not include a inject(service : MyInstanceIdService) method. Again, I don't see how/why this would compile. As already mentioned above, you registered a subcomponent (#ContributesAndroidInjector bindInstanceIdService) to inject your service, so you should use it. Your app component does not even have the correct scope to provide all of the objects.
Even worse, you create another AppComponent in your MyInstanceIdService, so even if this would work, you now have 2 singleton components providing 2 different instances of your "singleton" objects. Don't recreate components out of their scope. Use MyApplication.getAppComponent() and use that one!
So after removing the AccountModule from the AppComponent you should make your Application implement the HasServiceInjector, injecting/returning the dispatching injector like you do for your activities. Then inject your service by using AndroidInjection.inject(this).
To sum up:
Check your scopes. A #Singleton component can't provide #ViewScope things.
Don't register modules with different scopes on components, they can't use it anyways
Don't inject an object with a component of a different scope than you need
Don't recreate components when creating subcomponents. Use the existing one!
If you use dagger.android then don't create the components yourself, but implement the correct interface and use AndroidInjection.inject()