How to Isolate Hilt Modules - android

I'm using in my app hilt for di. I have a BasePresenter for a custom usage which takes as a parameter a List
abstract class BasePresenter(private val rules :MutableList<Rule>){...}
All of the concrete implementation Presenters should extend this one and provide the list like so
class FirstPresenter(rules: MutableList<Rule>) :BasePresenter<IrisView> (rules) {...}
class SecondPresenter(rules: MutableList<Rule>) :BasePresenter<IrisView> (rules) {...}
and each respective module is like so
#Module
#InstallIn(ActivityComponent::class)
object FirstModule {
#Provides
#ActivityScoped
fun provideFirstPresenter(rules: MutableList<Rule>) = FirstPresenter(rules)
#Provides
#ActivityScoped
fun provideRules(): MutableList<Rule> {
val rules = mutableListOf<Rule>()
rules.add(SpecificRule())
return rules
}
This works as expected, however when I try to add a second module for a different usecase like so
#Module
#InstallIn(ActivityComponent::class)
object SecondModule {
#Provides
#ActivityScoped
fun provideSecondPresenter(rules: MutableList<Rule>) = SecondPresenter(rules)
#Provides
#ActivityScoped
fun provideRules(): MutableList<Rule> {
val rules = mutableListOf<Rule>()
rules.add(OtherSpecificRule())
return rules
}
Hilt breaks because It seems that these two module have a different #provides method for
MutableList of rules. As I understand this happens because these two Modules exist in the same Scope ( for all Activities, as they are InstalledIn Activity component). So how Can I isolate those two Modules? In Dagger I assigned modules to components which were specific to the corresponding Feature, is that thing possible in Hilt, or should I address this in a different way?

Because Hilt returns the same type MutableList<Rule> in the same scope, I feel like Hilt is confusing which one to implement, because if a module provides something, it can be injected in a different module that is in equal scope. So, you can actually inject the returned list from fun provideRules() in the FirstModule and in the SecondModule, while providing the first or second presenter. That is why Hilt is generating errors, because it is not determined which one to implement.
If you want different objects, you can use the #Named annotation and pass the parameter to the function that you target. The code will turn into something like this:
First module:
#Module
#InstallIn(ActivityComponent::class)
object FirstModule {
#Provides
#ActivityScoped
fun provideFirstPresenter(#Named("firstPresenterRules") rules: MutableList<Rule>)
= FirstPresenter(rules)
#Provides
#ActivityScoped
#Named("firstPresenterRules")
fun provideRules(): MutableList<Rule> {
val rules = mutableListOf<Rule>()
rules.add(SpecificRule())
return rules
}
Second module:
#Module
#InstallIn(ActivityComponent::class)
object SecondModule {
#Provides
#ActivityScoped
fun provideSecondPresenter(#Named("secondPresenterRules") rules: MutableList<Rule>)
= SecondPresenter(rules)
#Provides
#ActivityScoped
#Named("secondPresenterRules")
fun provideRules(): MutableList<Rule> {
val rules = mutableListOf<Rule>()
rules.add(OtherSpecificRule())
return rules
}
Using the #Named annotation with different names allows you to have methods that return the same type, and also provide different ones as you desire.

Related

Dagger multibinding with a custom qualifier

I have the following interface called SettingHandler that is responsible for handling events related to a particular setting inside the Android app.
interface SettingHandler {
fun onHandleEvent(event: SettingEvent)
fun candleHandleEvent(event: SettingEvent): Boolean
fun getSettingId(): SettingId
}
Then let's say I have the following Dagger module:
#Module
object SettingsModule {
#Provides
fun provideSettingPresenter(
view: SettingsContract.View,
settingHandlers: Set<#JvmSuppressWildcards SettingHandler>
): SettingsPresenter {
//
}
#Provides
#IntoSet
fun provideSettingHandlerA(
dependencyA: A,
dependencyB: B
): SettingHandler {
//
}
#Provides
#IntoSet
fun provideSettingHandlerB(
settingHandlerC: Provider<SettingHandler>
): SettingHandler {
//
}
#Provides
#IntoSet
fun provideSettingHandlerC(
settingHandlerB: Provider<SettingHandler>
): SettingHandler {
//
}
}
As you can see, nothing too special here, expect for the SettingHandlerB and SettingHandlerC provider methods. They both depend on each other, so I've decided to resolve this circular dependency using the Dagger supplied Provider class. Since I require a concrete implementation of a setting handler (SettingHandlerC for the SettingHandlerB and SettingHandlerB for the SettingHandlerC), I now need to differentiate between them. That's where a #Qualifier annotation comes into play. I created the following qualifier annotation to differentiate among different implementations.
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION, AnnotationTarget.VALUE_PARAMETER)
annotation class SettingHandlerType(val id: SettingId)
SettingId is basically an enumeration that contains all possible setting constants. So now my SettingHandlers module looks as follows:
#Module
object SettingsModule {
#Provides
fun provideSettingPresenter(
view: SettingsContract.View,
settingHandlers: Set<#JvmSuppressWildcards SettingHandler>
): SettingsPresenter {
//
}
#Provides
#SettingHandlerType(SettingId.A)
fun provideSettingHandlerA(
dependencyA: A,
dependencyB: B
): SettingHandler {
//
}
#Provides
#SettingHandlerType(SettingId.B)
fun provideSettingHandlerB(
#SettingHandlerType(SettingId.C)
settingHandlerC: Provider<SettingHandler>
): SettingHandler {
//
}
#Provides
#SettingHandlerType(SettingId.C)
fun provideSettingHandlerC(
#SettingHandlerType(SettingId.B)
settingHandlerB: Provider<SettingHandler>
): SettingHandler {
//
}
}
And here comes the problem. Mutlibinding now does not work, because all my SettingHandlers are annotated with a #SettingHandlerType annotation and a Set that I'm injecting into the SettingsPresenter also needs to be annotated. However, annotating it with, for example, #SettingHandlerType(SettingId.A) won't work because in that case the Set will contain only setting handlers with that particular qualifier (SettingHandlerA, in this case). How can I construct a Set data structure using multibinding because I really don't want to provide another provider method where I'll be constructing it myself?
Any help would be much appreciated. Thanks in advance.
You want two different types of data: Individual handlers of type #SettingHandlerType(/*...*/) SettingHandler and a set of type Set<SettingHandler>. I think the best way would be to have each definition come with a #Binds #IntoSet complement.
#Binds #IntoSet abstract fun bindSettingHandlerA(
#SettingHandlerType(SettingId.A) handler: SettingHandler): SettingHandler
Unfortunately, since you've defined this as an object in Kotlin, it's slightly more complicated to put the necessarily-abstract #Binds methods on the same object. You may need to pursue one of these other solutions to make that topology work with your case, including the use of a companion object to capture the otherwise-static #Provides methods:
Dagger 2 static provider methods in kotlin
#Provides and #Binds methods in same class Kotlin
Dagger2 : How to use #Provides and #Binds in same module (java)

Dagger module depends on another module

I'm new to DI and Dagger.
I have this dependency graph in the Android project:
#Module(includes=[Module1, Module2, Module3]) ClassAModule
#Module(includes=[classAModule, Module4]) ClassBModule
#Module(includes=[ClassBModule]) ClassCModule
#Module(includes=[ClassBModule]) ClassDModule
Here's how Module3 looks like
#Module
class Module3 {
#Provides
fun provideUrl(): Url{
return ...
}
}
Module3's Url is required by ClassAModule,
But I want ClassCModule and ClassDModule to be able to provide different Url to ClassAModule
how should I approach this?
To get specific url for any module, you need to define annotation on provider method.
Example
#Provides
#Room
fun provideRoomWordDataSource(): WordDataSource {
return RoomWordDataSource()
}
#Provides
#Firestore
fun provideFirestoreWordDataSource(): WordDataSource {
return FirestoreWordDataSource()
}
#Singleton
class WordRepository
#Inject constructor(
#Room private val room: WordDataSource,
#Firestore private val firestore: WordDataSource
) : Repository<String, Word>(rx, rm), WordDataSource {
}
First two provider method has define WordDataSource instance of two difference class and define with two different annotation #Room and #Firestore.
To get two different WordDataSource in WordRepository, have just used #Room and #Firestore annotation in its constructor.
Enjoy the annotation power in Dagger. :)
Please feel free, if you need more details from me.

Dagger 2 and dependency injection hell?

How do you use dagger from Kotlin?
I've been in a loop of fixing one compile error and moving to another and at the end I get back to step 1
Here is all I need:
AppDependencies
GenericActivityDependencies
PerActivityDependency
Here are my main dependencies:
App
#Module
class ApplicationModule(private val application: Application) {
#Provides
#Singleton
fun provideContext(): Application = this.application
}
#Singleton
#Component(modules = [ HttpModule::class, ApplicationModule::class ])
interface AppComponent {
val app: Application
}
Why do I need to once provide the dependency in the module and another time define it in the component?
Activity Module
#Module
class ActivityModule(private val activity: Activity) {
#PerActivity
#Provides
#ActivityContext
fun provideContext(): Context = activity
}
#Component(modules = [ActivityModule::class], dependencies = [AppComponent::class])
#ActivityContext
interface ActivityComponent {
fun inject(activity: MainActivity)
}
HomeModule
#Module
class LandingModule {
#PerActivity
#Provides
fun provideSomethig(): Something {
return Something()
}
}
#SomeActivity
#Subcomponent(modules = [LandingModule::class])
interface LandingSubcomponent {
val something: Something
}
By this point, I have written more code than there needs to be in my whole activity.
I get errors like can't inherit from a scopes component
Can't generate Dagger gencode
Subcomponent needs a different scope
How do I achieve this?
Is there a better di for kotlin?
Is there a sample somewhere I could follow that has per activity module?
I understand your frustrations. I have been there before and it took me quite some time to understand dagger myself. Just a quick demo/tutorial.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun context(): Context
}
#Module
class AppModule(private val application: Application) {
#Provides
#Singleton
fun provideApplication(): Application= application
}
The component is the interface to the container. Anything defined in here can be accessible if you are able to instantiate your container successfully. Also, it is the interface to other containers/components. This means that if you want to expose something outside your container, you define it here. Therefore,
Why the heck do I need to once provide the dependency in the module
and another time define it in the component. This is plain stupid.
is not always true. You don't need to define anything in your component if you don't want anything to expose outside. An alternative to exposing would be injecting.
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(activity: MainActivity)
}
You are not exposing anything here but you can still get the activity context from the container thru inject.
Now let's proceed to scoping.
Scoping is the way to provide 'local singletons' inside your container. A scoped dependency will be created only once inside the container. An example is your PerActivity scope. A scoped component will only accept a module that is scoped with the same Scope. For example:
#PerActivity
#Component(dependencies = [AppComponent::class],
modules = [ActivityModule::class])
interface ActivityComponent{
fun inject(activity: MainActivity)
}
The corresponding module should only be scoped with PerActivity as well.
class ActivityModule(activity:Activity) {
#PerActivity
#Provides
fun provideActivity() = activity
}
Any other scope defined in your module that is not the same scope as your intended component will result in a compile error. Multiple scopes is not allowed as well.
As for the component dependencies, you can use use dependencies or subcomponents. If dependencies is used, any dependency that is required by the child must be exposed by the parent. In our case above, if the ActivityComponent requires the activity context, the AppComponent must define a function that returns it. In subcomponents, just define your subcomponent in your component and the dependencies will be resolved internally.
I have written a small guide to learning dagger 2. If you are interested, you can go check it out.
https://medium.com/tompee/android-dependency-injection-using-dagger-2-530aa21961b4
https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781
Why the heck do I need to once provide the dependency in the module and another time define it in the component. This is plain stupid.
I agree, but don't be discouraged by that fact, because once you learn to master it you'll learn to appreciate it and really take advantage. I've been using version 2.2 for a while without any issues. I've only needed to define one annotation, add a pair of additional dependencies (AutoDagger among them, which takes care of that component feature), and use the following structure:
/* Dagger */
implementation "com.google.dagger:dagger:2.2"
// Fix: github.com/rharter/auto-value-gson/issues/43#issuecomment-219994018
kapt 'com.squareup:javapoet:1.9.0'
kapt "com.google.dagger:dagger-compiler:2.2"
compileOnly 'org.glassfish:javax.annotation:10.0-b28'
/* Autodagger */
kapt "com.github.lukaspili.autodagger2:autodagger2-compiler:1.1"
implementation "com.github.lukaspili.autodagger2:autodagger2:1.1"
DaggerScope.java
#Retention(RetentionPolicy.RUNTIME)
#Scope
#interface DaggerScope {
}
YourApp.kt
#AutoComponent(modules = [YourApp.Module::class])
#AutoInjector
#DaggerScope
class YourApp : Application() {
...
#Singleton #dagger.Module
inner class Module(private val app : YourApp) {
#Provides #AutoExpose(YourApp::class) fun application(): Application = app
#Provides #AutoExpose(YourApp::class) fun context(): Context = app
...
// Stuff like your database or base service can go here
}
}
SomeActivity.kt
#AutoComponent(dependencies = [YourApp::class],
modules = [SomeActivity.Module::class]) // you are free to add other modules here
#AutoInjector
#DaggerScope
class SomeActivity : AppCompatActivity() {
...
#dagger.Module
inner class Module() {
#Provides #AutoExpose(SomeActivity::class) fun something(): Something {
return some way of creating Something
}
/* specific deps for SomeAcitivity's actions, like specific services.
You can also access DAOs as you've got access to the DB */
}
}
You can also mimic this structure with a Fragment instead of an Activity.
Hope this helps you!

Override dependencies in Dagger Module

There's a base module with common dependencies:
#Module
object CommonActivityModule {
#JvmStatic
#Provides
fun baseNavigator(activity: AppCompatActivity): Navigator = BaseNavigator(activity, SOME_STUFF)
// other common deps
}
I include it in every Activity module to obtain those common deps. But in some modules I want to shadow a few base interface implementations with another:
#Module(includes = [CommonActivityModule::class])
interface SomeActivityModule {
#Binds
fun anotherNavigator(anotherNavigator: AnotherNavigator): Navigator
// other module's binds
}
And it thows ..Navigator is bound multiple times-exception. Is there a way how I can replace those interface implementations without dropping the whole CommonActivityModule?
You're binding each as Navigator. I believe you need to use a different return type on your shadowed binding.
Alternatively, you could try something with Qualifiers. Defining a custom qualifier is easy; you should be able to find examples online. I'd share one, but I'm on my phone right now.
This answer has been accepted, so I'd like to add some code to make it more "complete". Here's an example of a custom "Qualifier" (Kotlin)
import javax.inject.Qualifier
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class DelayQualifier
Usage:
#Module object {
#Provides #DelayQualifier #JvmStatic
fun provideDelay(): Long = if (BuildConfig.DEBUG) 1L else 3L
}
#ActivityScoped
class SignupViewModelFactory #Inject constructor(
#param:DelayQualifier private val delay: Long
) : ViewModelProvider.Factory { ... }
This is the only Long I'm currently injecting in my project, so I don't need the qualifier. But if I decide I want more Longs, I'll regret not qualifying this one.

Dagger 2 Scopes and Dependencies in Kotlin

I'm looking to add Dagger 2 to an existing app and am running into issues with component dependencies and scoping once I get more that 2 levels down. My thought process is to have an AppComponent, LandingActivityComponent and LandingFragmentComponent (not great names right now but we're early) with the scopes #Singleton, #ActivityScope and #FragmentScope respectively. Everything I've done works if I stop at the activity, but once I add the fragment level I get the following error:
e: /Users/me/git/myapp-android/myapp/build/tmp/kapt3/stubs/regularDebug/tv/myapp/android/app/core/dagger/LandingActivityComponent.java:17: error: com.myapp.android.app.core.LandingActivity cannot be provided without an #Inject constructor or from an #Provides-annotated method. This type supports members injection but cannot be implicitly provided.
public abstract com.myapp.android.app.core.LandingActivity landingActivity();
^
com.myapp.android.app.core.LandingActivity is provided at
com.myapp.android.app.core.dagger.LandingActivityComponent.landingActivity()
e: /Users/me/git/myapp-android/myapp/build/tmp/kapt3/stubs/regularDebug/tv/myapp/android/app/core/dagger/LandingFragmentComponent.java:13: error: com.myapp.android.app.settings.QuickSettingsPresenter cannot be provided without an #Inject constructor or from an #Provides- or #Produces-annotated method.
public abstract void inject(#org.jetbrains.annotations.NotNull()
^
com.myapp.android.app.settings.QuickSettingsPresenter is injected at
com.myapp.android.app.profile.ProfileViewPagerFragment.mQuickSettingsPresenter
com.myapp.android.app.profile.ProfileViewPagerFragment is injected at
com.myapp.android.app.core.dagger.LandingFragmentComponent.inject(profileViewPagerFragment)
Application
#Module
class AppModule(private val app: MyApplication) {
#Provides #Singleton
fun provideApp(): MyApplication = app
#Provides #Singleton
fun provideContext(): Context = app.applicationContext
#Provides #Singleton
fun provideAccountManager(): AccountManager = AccountManager(app)
}
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
fun inject(application: MyApplication)
// Allow dependents to see the properties provided below
fun accountManager(): AccountManager
}
Activity
#Scope
#Qualifier
#Retention(RUNTIME)
annotation class ActivityScope
#Module
class LandingActivityModule(private val landingActivity: LandingActivity) {
#Provides #ActivityScope
fun provideLandingActivity(): LandingActivity = landingActivity
}
#ActivityScope
#Component(dependencies = [AppComponent::class], modules = [LandingActivityModule::class])
interface LandingActivityComponent {
fun inject(landingActivity: LandingActivity)
// Allow dependents to see the properties provided below
fun landingActivity(): LandingActivity
fun accountManager(): AccountManager
}
Fragment
#Scope
#Qualifier
#Retention(RUNTIME)
annotation class FragmentScope
#Module
class LandingFragmentModule(private val landingFragment: LandingFragment) {
#Provides #FragmentScope
fun provideFragment(): LandingFragment = landingFragment
#Provides #FragmentScope
fun provideQuickSettings(activity: LandingActivity): QuickSettingsPresenter =
QuickSettingsPresenter.create(activity)
}
#FragmentScope
#Component(dependencies = [LandingActivityComponent::class], modules = [LandingFragmentModule::class])
interface LandingFragmentComponent {
fun inject(profileViewPagerFragment: ProfileViewPagerFragment)
}
I feel like I'm probably missing something fundamental or need to structure things a little differently but I think this highlights what I'm going for.
Interestingly, if I remove the #Scope annotations for the #Provides in the activity and fragment modules, everything is fine. And as I mentioned, if I cut out the fragment module, I can scope the activity no problem.
The end goal here is to have these components work with presenters as well but I'm kind of going step by step. I'm new to non-monolithic components in Dagger and haven't found a guide or post that's made this click for me.
(Posted on behalf of the question author).
I figured out what's going on. Somewhere along the way I read something that lead me to believe I needed #Qualifier as a part of my scopes but that isn't the case. Removing it seems to have fixed my problem. Will leave this here as a dependency based approach for anyone looking.

Categories

Resources