Using dependencies from multiple modules dagger 2.11 android - android

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()

Related

Hilt Testing - Replace internal Hilt-Module in separate Android multi-module app

I have an Android app where the codebase is split into 2 different modules: App and Domain
Goal: I am attempting to use the Hilt provided testing functionality to replace a Domain internal dependency when creating App tests.
In Domain, I have an internal interface with an internal implementation, like below:
internal interface Database {
fun add(value: String)
}
internal class DatabaseImpl #Inject constructor() : Database {
override fun add(value: String) { ... }
}
The above guarantees that the Database can only be used inside Domain and cannot be accessed from elsewhere.
In Domain I have another interface (which is not internal), for use in App, with an internal implementation, like below:
interface LoginService {
fun userLogin(username: String, password: String)
}
internal class LoginServiceImpl #Inject constructor(database: Database) {
override fun userLogin(username: String, password: String) {
// Does something with the Database in here
}
}
In Domain I use Hilt to provide dependencies to App, like below:
#Module(includes = [InternalDomainModule::class]) // <- Important. Allows Hilt access to the dependencies provided by InternalDomainModule.
#InstallIn(SingletonComponent::class)
class DomainModule {
...
}
#Module
#InstallIn(SingletonComponent::class)
internal class InternalDomainModule {
#Provides
#Singleton
fun provideDatabase() : Database = DatabaseImpl()
#Provides
#Singleton
fun provideLoginService(database: Database) : LoginService = LoginServiceImpl(database)
}
This all works perfectly in isolating my implementations and only exposes a single interface outside of Domain.
However, when I need to provide fake implementations inside App using the Hilt guidelines, I am unable to replace the LoginService as I do not have access to InternalDomainModule (because it is internal to Domain only) and replacing DomainModule does not replace LoginService (as it is provided in another Hilt module, namely InternalDomainModule), like below:
#Module
#TestInstallIn(
components = [SingletonComponent::class],
replaces = [DomainModule::class] // [InternalDomainModule::class] is impossible as inaccessible in **App**
)
class FakeModule {
#Provides
#Singleton
fun provideFakeLoginService() : LoginService = FakeLoginServiceImpl() <- Something fake
}
The above leads to only DomainModule being replaced, not InternalDomainModule, which leads to LoginService being provided twice, which makes Hilt unhappy.
Making things not internal to Domain fixes the issue, but defeats the purpose of having a multi-module Android app with clear separations.

Dagger hilt: Difference between annotating a class #Singleton and a provides function #Singleton

My question is pretty simple and straightforward: What is the difference between the two annotations / examples:
Example one
#Singleton
class MySingletonClass() {}
#Module
#InstallIn(FragmentComponent::class)
abstract class MyFragmentModule {
#Provides
fun provideMySingletonClass() = MySingletonClass()
}
Eaxmple two
class MySingletonClass() {}
#Module
#InstallIn(FragmentComponent::class)
abstract class MyFragmentModule {
#Singleton
#Provides
fun provideMySingletonClass() = MySingletonClass()
}
The only difference I know is, that the second example gives me the following error:
error: [Dagger/IncompatiblyScopedBindings] FragmentC scoped with #dagger.hilt.android.scopes.FragmentScoped may not reference bindings with different scopes:
Does that mean, that the #Singleton annotation in example one is simply ignored?
In Example One, your #Singleton annotation is ignored, but only because you are calling the constructor yourself in your #Provides method. Because Dagger doesn't interact with your MySingletonClass constructor, it cannot read or use the annotation.
If your #Singleton class MySingletonClass had an #Inject constructor—even an empty one—then Dagger would be able to interact with it directly as long as you also delete the #Provides fun that would override the constructor detection. Once you've done that, the behavior of #Singleton would be the same in either syntax.
Regarding the error message "error: [Dagger/IncompatiblyScopedBindings] XXX scoped with #YYY may not reference bindings with different scopes": #Andrew The real problem here is that in Example Two you're trying to declare a #Singleton binding in a Module that you install in your FragmentComponent. #Singleton bindings can only happen in a #Singleton component, which in Hilt is SingletonComponent. I don't remember for sure, but I think your Example One (with the edits I described) would work with singleton behavior and without an error, because Dagger would automatically select the appropriate component in the hierarchy to install your MySingletonClass.

Problems with Hilt DI

Sorry for seally question, I have never used Dagger/Hilt before. Can't understand how to inject dependencies in my app. Here is what have now:
#InstallIn(Application::class)
#Module
abstract class RepositoryModule {
#Binds
abstract fun bindProductRepository(productRepository: ProductRepository): IProductRepository
#Binds
abstract fun bindCategoryStorage(categoryStorage: CategoryStorageImpl) : CategoryStorage
companion object {
#Provides
#Singleton
fun createRoomDataBase(#ApplicationContext context: Context) : ProductRoomDatabase = ProductRoomDatabase.getDatabase(context)
#Provides
#Singleton
fun createProductDao(productRoomDatabase: ProductRoomDatabase) = productRoomDatabase.productDao()
#Provides
#Singleton
fun createCategoryDao(productRoomDatabase: ProductRoomDatabase) = productRoomDatabase.categoryDao()
}
}
Hilt comes with three pre-made components:
ApplicationComponent | SingletonComponent - component that contains application-level dependencies and live until application lives.
ActivityComponent - component that contains Activity-level dependencies
FragmentComponent - component that contains Fragment level dependencies
InstallIn annotation suggests that all dependendencies in the current module will be available in the argument of the annotation.
There are many courses and tutorials about this topic, but most of them just rephrase the original documentation. I would strongly suggest you read this documentation, from official Android website
https://developer.android.com/training/dependency-injection/hilt-android

What does it basically mean with scope annotations in two components (one depends on the other)

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 { ... }

Dagger Injection for dependent components

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?

Categories

Resources