Dagger 2 and dependency injection hell? - android

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!

Related

How to Isolate Hilt Modules

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.

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.

How to provide a dependency with #SessionScope and #ActivityScope using Dagger 2.10 Android Injector?

Suppose you have an application where user logs in on the first screen and, from that moment on, you have access to an User object. I would like to provide this dependency under a #SessionScope - it means, when user logs out, all dependencies provided through a component annotated as #SessionScope would die.
Despite the dependencies provides via #SessionScope I would like to have dependencies provided via #ActivityScope, an ActivityPresenter for instance and, of course, I would have to provide dependencies from #SessionScope and #ActivityScope together to an Activity consumer class.
What is the best to do it using Dagger 2 new AndroidInjector feature?
So far I'm able to provide dependencies under #ActivityScope like shown below:
#Scope
#Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
#Module
abstract class ActivitiesBuilder {
#ActivityScope
#ContributesAndroidInjector(modules = arrayOf(HomepageModule::class, FacebookModule::class))
abstract fun providesHomepageViewImpl(): HomepageViewImpl
}
#Module
abstract class AppModule {
#Provides
#Singleton
fun provides (application: Application) : Context = application
}
#Singleton
#Component(
modules = arrayOf(
AndroidInjectionModule::class,
ActivitiesBuilder::class,
AppModule::class
)
)
interface AppComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
}
class App : DaggerApplication() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> = DaggerAppComponent.builder().create(this)
}
Tutorial
Create a custom scope named #SessionScope;
Create a class named as SessionActivityBinding:
add a method that will bind the activity sub component that will be provided under session scope - further details on this implementation can be found here;
Create a subcomponent named SessionComponent:
annotate this subcomponent with the custom scope created on step 1;
create a #Subcomponent.Builder and add a method that will receive a User object as parameter;
annotate this method with #BindsInstance;
add this method fun build(): SessionComponent in order to allow the provision of this SessionComponent - this will be necessary in order to release this component when user loggs out;
add SessionActivityBinding class into the module array;
[OPTIONAL]: Add a SessionModule class to provide dependencies that should be provided under #SessionScope;
On AppModule add SessionComponent as a subcomponent;
Add the AppModule in the list of module's from AppComponent;
When user successfully authenticate into the system, and an User object is acquired, create an instance of SessionComponent that must remain live until user logs out;
Whenever user logs out, discard the old instance of SessionComponent like: sessionComponent = null - this will discard all dependencies provided under #SessionScope;
Whenever user logs in again, repeat the process from step 6;
Further details and examples can be found here;

How inject the Activity to object that is being injected to the Activity?

I have this module:
#Module
public class UserProfileModule {
#Provides
#Singleton
UserProfileController providesUserProfileController() {
return new UserProfileController();
}
}
and this component:
#Component(modules = {UserProfileModule.class})
#Singleton
public interface AppComponent {
void inject(UserProfileActivity activity);
}
So far, in My UserProfileActivity I can #Injectan UserProfileController. But now, I need to inject the UserProfileActivity to the controller. I mean, inject each other.
I could do it by calling a UserProfileController setter in UserProfileActivity: setActivity(this);, but it would be nice if can be automatic.
How can achieve that?
Thanks.
For starters: add it to the constructor. Then declare that dependency.
#Provides
#Singleton
UserProfileController providesUserProfileController(UserProfileActivity activity) {
return new UserProfileController(activity);
}
After doing so dagger will complain about not being able to provide UserProfileActivity unless you already do so. If you don't, add another module, or just provide the dependency from that same module. The actual implementation follows, first we need to fix your code.
#Singleton is a dependency on top of the hierarchy. You can't—or at least should not—have an activity dependency for a #Singleton annotated object, since this will probably cause bad smells and/or memory leaks. Introduce a custom scope #PerActivity to use for dependencies within your activities lifespan.
#Scope
#Retention(RUNTIME)
public #interface PerActivity {}
This will allow for correct scoping of the object. Please also refer to some tutorials about dagger, since this is a really important issue and covering everything in a single answer would be too much. e.g. Tasting dagger 2 on android
The following uses the latter approach of the aforementioned 2 options by expanding your module:
#Module
public class UserProfileModule {
private final UserProfileActivity mActivity;
public UserProfileModule(UserProfileActivity activity) {
mActivity = activity;
}
#Provides
#PerActivity
UserProfileActivity provideActivity() {
return mActivity;
}
#Provides // as before
#PerActivity
UserProfileController providesUserProfileController(UserProfileActivity activity) {
return new UserProfileController(activity);
}
}
If you now use your component Builder you can create a new instance of your module with the activity as an argument. The dependency will then correctly be supplied.

Categories

Resources