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.
Related
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.
I am experimenting with Dagger subcomponents and got an error that I am having some difficulties understanding.
So basically I have a Subcomponent and a Component.
// FeatureComponent.kt, #Subcomponent
#Scope
annotation class Feature
#Subcomponent(modules = [FeatureModule::class])
#Feature
interface FeatureComponent {
fun inject(loginActivity: LoginActivity)
#Subcomponent.Builder
interface Builder {
fun build(): FeatureComponent
}
}
#Module
class FeatureModule {
#Provides
#Feature
fun provideFeatureStorage(): FeatureStorage {
return FeatureStorage()
}
}
#Feature
class FeatureStorage
and the Component:
#Component(modules = [LoginModule::class])
#Singleton
interface LoginComponent {
fun loginComponent(): LoginComponent
fun inject(loginActivity: LoginActivity)
#Component.Builder
interface Builder {
fun build(): LoginComponent
}
fun featureComponent(): FeatureComponent.Builder // declare the subcomponent
}
#Module(subcomponents = [FeatureComponent::class])
class LoginModule {
#Provides
#Singleton
fun provideSingletonInstance(): Storage {
return Storage()
}
#Provides
fun provideNotSingletonInstance(): UserSession {
return UserSession()
}
}
class Storage
class UserSession
And I am trying to inject the FeatureStorage, which is provided by the #Subcomponent, in an activity like this:
class LoginActivity : AppCompatActivity() {
#Inject
lateinit var featureStorage: FeatureStorage
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
loginComponent.inject(this)
loginComponent.featureComponent().build().inject(this)
}
}
But Dagger compilation fails with:
[Dagger/MissingBinding] com.vgrec.daggerkurs.components.FeatureStorage
cannot be provided without an #Inject constructor or an
#Provides-annotated method.
A binding with matching key exists in component:
com.vgrec.daggerkurs.components.FeatureComponent
com.vgrec.daggerkurs.components.FeatureStorage is injected at
com.vgrec.daggerkurs.components.LoginActivity.featureStorage
com.vgrec.daggerkurs.components.LoginActivity is injected at
com.vgrec.daggerkurs.components.LoginComponent.inject(com.vgrec.daggerkurs.components.LoginActivity)
This part: FeatureStorage cannot be provided without an #Inject constructor or an #Provides-annotated method seems strange, because FeatureStorage IS provided using the #Provides annotation.
Any ideas what could potentially be wrong in my setup?
Dagger can't partially inject a class; FeatureComponent knows how to inject FeatureStorage, but you've instructed LoginComponent to try to inject LoginActivity, so LoginComponent is going to unsuccessfully search for a #Provides FeatureStorage method.
Since you can create a FeatureComponent as a subcomponent of LoginComponent, there should be no bindings that LoginComponent can inject that FeatureComponent cannot. Therefore, I'd drop the inject method from LoginComponent and allow your created FeatureComponent to be the sole class that injects LoginActivity.
As an alternative, you can drop the #Inject from featureStorage in LoginActivity and instead expose a featureStorage() method on FeatureComponent. In that case, rather than instantiating FeatureStorage via inject(LoginActivity), you can save and instantiate it yourself.
However, in all cases I have to admit that I find you graph confusing, and I expect other readers would too: The relationship between FeatureComponent and LoginComponent is unclear, and I wouldn't know why those lifecycles would be separate or really what kind of lifecycle FeatureComponent would be expected to have in the first place. In Android, it is much more common to set up a Dagger graph structure that matches Application and Activity lifecycles, and systems like Hilt make those components built-in scopes in the framework.
I am getting a dependency cycle whenever I try to use a subcomponent with binding objects. I have an app scope and an activity scope. At the app scope I create my web service then when the activity opens I want to create a storage object, controller, and navigator (all custom classes not androidx classes) and inject them into my androidx ViewModel class. But when I do so I get a dependency cycle.
My top level component looks like
#AppScope
#Component(modules = [AppModule::class])
interface AppComponent {
val activityComponentBuilder: ActivityComponent.Builder
}
#Module(subcomponents = [ActivityComponent::class])
interface AppModule {
#Binds
fun mockWebService(mockWebService: MockWebService): MockWebService
}
Next my subcomponent looks like
#ActivityComponent
#Subcomponent(modules = [ActivityModule::class])
interface ActivityComponent {
fun inject(sharedViewModel: SharedViewModel)
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun storage(storage: Storage): Builder
fun build(): ActivityComponent
}
}
In my activity module I bind two objects
#Binds
abstract fun controller(controller: Controller): Controller
#Binds
abstract fun navigator(navigator: Navigator): Navigator
Each object has an #Inject constructor
class Navigator #Inject constructor(private val storage: Storage)
class Controller #Inject constructor(
private val webService: MockWebService,
private val navigator: Navigator,
private val storage: Storage
) {
Inside my shared view model I try to build my component and inject the fields
#Inject
lateinit var navigator: Navigator
#Inject
lateinit var controller: Controller
init {
MainApplication.component.activityComponentBuilder
.storage(InMemoryStorage.from(UUID.randomUUID().toString()))
.build()
.inject(this)
}
But dagger won't build. I get an error
[Dagger/DependencyCycle] Found a dependency cycle: public abstract interface AppComponent {
MockWebService is injected at di.AppModule.mockWebService(mockWebService)
MockWebService is injected at ActivityModule.Controller(webService, …)
Controller is injected at SharedViewModel.controller
SharedViewModel is injected at
But the error message cuts off there. Am I missing something in how to use a subcomponent to put objects on the graph and then inject them into an object? Is this not possible with Dagger?
#Binds is used to let dagger know the different implementations of an interface. You don't need #Binds here since Navigator and Controller are simple classes that do not implement any interface. I'd assume that's the case with MockWebService too. Also, those classes have #Inject constructor, which means dagger can instantiate them and we don't need to write extra #Provides functions for those classes.
#Binds isn't doing any scoping. Its only job is to tell dagger about different implementations. You can add #XScope with #Binds to make some object scoped. Or, you could just add the scope annotation to the class declaration. Here's an example of how you can add scope to class declaration.
As for the dependency cycle, I think it's because you're telling ActivityComponent to use ActivityModule and telling ActivityModule to install ActivityComponent. Doing just either one should be the case (I think).
The Problem
When attempting to add a ViewModel bind into the multibinding for an inherited ViewModelFactory (created with no scope) within a lower scope (#FragmentScope), I keep running into this error:
java.lang.IllegalArgumentException: unknown model class com.example.app.MyFragmentVM
What I've read or tried
(note: the below is not by any means an exhaustive list, but are two good examples of resources and the kinds of advice I've perused)
[1] dagger2 and android: load module which injects viewmodel on a map (and other variants / similar Q and As)
[2] https://medium.com/tompee/dagger-2-scopes-and-subcomponents-d54d58511781
I'm relatively new to working with Dagger so I had to do a lot of Googling to try and understand what has been going on, but I've reached a point where, to my understanding, something should be working(?)...
From sources similar to [1], I removed the #Singleton scope on ViewModelFactory, but I still get the aforementioned crash saying there is no model class found in the mapping.
From sources similar to [2] I tried to reinforce my understanding of how dependencies worked and how items are exposed to dependant components. I know and understand how ViewModelProvider.Factory is available to my MyFragmentComponent and it's related Modules.
However I do not understand why the #Binds #IntoMap isn't working for the MyFragmentVM.
The Code
Let me first go through the setup of the stuff that already exists in the application -- almost none of it was scoped for specific cases
// AppComponent
#Component(modules=[AppModule::class, ViewModelModule::class])
interface AppComponent {
fun viewModelFactory(): ViewModelProvider.Factory
fun inject(activity: MainActivity)
// ... and other injections
}
// AppModule
#Module
class AppModule {
#Provides
#Singleton
fun providesSomething(): Something
// a bunch of other providers for the various injection sites, all #Singleton scoped
}
// ViewModelModule
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindsViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityVM::class)
abstract fun bindsMainActivityVM(vm: MainActivityVM): ViewModel
}
// VMFactory
class ViewModelFactory #Inject constructor(
private val creators: Map<Class<out ViewModel>, #JvmSuppressWildcards Provider<ViewModel>>
): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
modelClass.isAssignableFrom(it.key)
}?.value ?: throw IllegalArgumentException("unknown model class $modelClass")
try {
#Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
And the following is how I am trying to add and utilize my #FragmentScope:
// MyFragmentComponent
#FragmentScope
#Component(
dependencies = [AppComponent::class],
modules = [MyFragmentModule::class, MyFragmentVMModule::class]
)
interface MyFragmentComponent {
fun inject(fragment: MyFragment)
}
// MyFragmentModule
#Module
class MyFragmentModule {
#Provides
#FragmentScope
fun providesVMDependency(): VMDependency {
// ...
}
}
// MyFragmentVMModule
#Module
abstract class MyFragmentVMModule {
#Binds
#IntoMap
#ViewModelKey(MyFragmentVM::class)
abstract fun bindsMyFragmentVM(vm: MyFragmentVM): ViewModel
}
// MyFragment
class MyFragment : Fragment() {
#set:Inject
internal lateinit var vmFactory: ViewModelProvider.Factory
private lateinit var viewModel: MyFragmentVM
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerMyFragmentComponent.builder()
.appComponent(MyApplication.instance.component)
.build()
.inject(this)
viewModel = ViewModelProvider(this, vmFactory).get(MyFragmentVM::class.java)
}
}
What's interesting here to note is that MyFragmentModule itself does NOT end up providing any unique injections for MyFragment (those all come from AppComponent as it is right now). It DOES however, provide unique injections for the ViewModel that MyFragment uses.
The root of this problem is the difference between subcomponents and component dependencies.
Subcomponents
When working with subcomponents, the parent component knows everything about its subcomponents. As such, when a subcomponent requests a multibinding, the parent component can combine its contributions with those of the subcomponent. This even works transitively: if the subcomponent requests an unscoped ViewModelProvider.Factory, the injected map will include bindings from the subcomponent. (The same is true of a #Reusable binding, but not a #Singleton.)
If you change your components with dependencies into subcomponents, everything will just work. However, this might not fit your desired architecture. In particular, this is impossible if MyFragmentComponent is in an Instant App module.
Component dependencies
When working with component dependencies, the main component merely exposes objects through provision methods, and it does not know about any components that might depend on it. This time, when asked for a ViewModelProvider.Factory, the main component does not have access to any #ViewModelKey bindings except its own, and so the Factory it returns will not include the MyFragmentVM binding.
If MyFragmentComponent does not require any ViewModel bindings from AppComponent, you can extract bindsViewModelFactory into its own module and include it in both components. That way, both components can create their own Factory independently.
If you do need some ViewModel bindings from AppComponent, hopefully you can add those binding modules to MyFragmentComponent as well. If not, you would have to expose the map in AppComponent, and then somehow combine those entries with your new bindings. Dagger does not provide a good way to do this.
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!