In my app I have a component with Application scope (same as Singleton) that provides a ViewModel Factory, and a dependent component with Activity scope that injects the factory in a fragment.
The application component is defined as follows:
#Component(modules = [AppModule::class, /* other stuff */, ViewModelModule::class])
#ApplicationScope
interface AppComponent {
fun inject(app: Application)
/* other stuff */
val viewModelFactory: ViewModelFactory
}
The view model module is defined as follows:
#ApplicationScope
#Module
abstract class ViewModelModule {
#Binds
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(DisplayEntryViewModelImpl::class)
abstract fun bindDisplayEntryViewModel(displayEntryViewModelImpl: DisplayEntryViewModelImpl): ViewModel
}
The activity scope component is defined as follows:
#Component(dependencies = [AppComponent::class], modules = [DisplayEntryActivityModule::class])
#ActivityScope
interface DisplayEntryActivityComponent {
fun inject(displayEntryActivity: DisplayEntryActivity)
fun inject(displayEntryFragment: DisplayEntryFragment)
}
When I try to inject the viewmodel factory in the fragment I get this error:
error: android.arch.lifecycle.ViewModelProvider.Factory cannot be provided without an #Provides- or #Produces-annotated method.
If I update the activity component to include the view model module, like this
#Component(dependencies = [AppComponent::class], modules = [DisplayEntryActivityModule::class, ViewModelModule::class])
#ActivityScope
interface DisplayEntryActivityComponent {
fun inject(displayEntryActivity: DisplayEntryActivity)
fun inject(displayEntryFragment: DisplayEntryFragment)
}
Then it compiles. My understanding is that dependent components have access to the injected members from the parent component if the parent component explicitly provides those members, as I do here with the
val viewModelFactory: ViewModelFactory
so why do I still need to provide the viewmodel module in the activity scope component?
When using dependencies, dagger will use that component to inject member.
Parent component must explicitly declare objects which can be used in child components.
#Component(modules = [AppModule::class, /* other stuff */, ViewModelModule::class])
#ApplicationScope
interface AppComponent {
fun inject(app: Application)
fun viewModelFactory(): ViewModelProvider.Factory
fun viewModel(): ViewModel
}
You can take a look at this article:
https://proandroiddev.com/dagger-2-part-ii-custom-scopes-component-dependencies-subcomponents-697c1fa1cfc
Related
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).
I am trying to add dagger-android to a Kotlin project and got confused when it is requied to create a Module and when it is sufficient to just declare an Inject contructor.
Assume there is the following dependency graph:
Activity
-> ViewModel
-> Repository
-> Webservice
-> Dao
-> Database
-> Application
To provide ViewModel for the Activity we create respective modules for the activity and ViewModel factory, and then create the ViewModel in the Activity manually like so:
#Module
abstract class ActivityModule {
#ContributesAndroidInjector
abstract fun mainActivity(): MainActivity
}
// Skiping ViewModelKey and ViewModelFactory code for brevity
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(
factory: ViewModelFactory
): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainViewModel::class)
internal abstract fun mainViewModel(viewModel: MainViewModel): ViewModel
}
class MainActivity : DaggerAppCompatActivity() {
#Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(HomeViewModel::class.java)
}
}
To provide Repository for the ViewModel we just declare #Inject constructor like so:
class MainViewModel #Inject constructor(private val repository: Repository): ViewModel() {}
To provide Webservice and Dao for the Repository as well as Database for the Dao we create respective Modules like so:
#Module
class NetworkModule {
#Provides
#Singleton
fun provideWebservice() = Webservice.create()
}
interface Webservice {
...
companion object Factory {
fun create(): Webservice {
...
return retrofit
}
}
}
#Module
class DataModule {
#Provides
#Singleton
fun provideApplicationDatabase(app: Application) =
AppDatabase.getDatabase(app)
#Provides
#Singleton
fun provideUserDao(db: AppDatabase) = db.userDao()
}
#Database(...)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
fun getDatabase(context: Context): AppDatabase {
...
return instance
}
}
}
And the Application is provided for the Dabatabase by some magic in the AppComponent and the Application class
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
NetworkModule::class,
DataModule::class,
ViewModelModule::class,
ActivityModule::class
])
interface AppComponent: AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun create(application: Application): Builder
fun build(): AppComponent
}
}
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
DaggerAppComponent.builder().create(this).build()
}
The questions are:
How does the Database get the Application instance? Is it AndroidSupportInjectionModule which does the magic?
Why do we need to create Modules for the Webservice and the Database but not the Repository?
Is it possible to annotate the Webservice interface and the Database class themselves to skip creating separate dagger modules for them?
Question 1 How does the Database get the Application instance? Is it AndroidSupportInjectionModule which does the magic?
Answer: No it is not the work of AndroidSupportInjectionModule. AndroidSupportInjectionModule is included in Dagger Android Support which helps in
"Configures bindings to ensure the usability of dagger.android and dagger.android.support framework classes." Found Here
So, basically you just pass the Application context when you're creating Dagger Builder you just pass it from Application Class now Once you got it in the Main Component you have the Application context in your all Modules and we need context when we initialize Room Database.
Question 2 Why do we need to create Modules for the Webservice and the Database but not the Repository? Is it possible to annotate the Webservice interface and the Database class themselves to skip creating separate dagger modules for them?
Answer:
First, always try to achieve constructor Injection. The general idea of Modules is "If we don't own the class we can't #annotate its constructor so we make Modules to provide their implementation". also if we want to inject Interface we can achieve it via its implementation class by its constructor Injection.
So we don't own the initialization of WebService and Database that is why we create their Modules and provide them so we can get their instances in our repositories. We own our repository class so we can Inject them via constructor Injection.
I've started using Dagger2, so there's still a lot to learn. I'm wondering if someone could point me on the right direction.
So, I've created a module for registering the view models used by my activities. It looks like this:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
#Binds
#IntoMap
#ViewModelKey(ShowDetailsViewModel::class)
abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}
ViewModelKey is a simple helper annotation class which looks like this:
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class ViewModelKey (val value: KClass<out ViewModel>) {
}
The ViewModelModule is loaded by my main app component (used for creating the app):
#Singleton
#Component(
modules=[
AndroidSupportInjectionModule::class,
AppModule::class,
DatabaseModule::class,
NewsServiceModule::class,
JobBindingModule::class,
ViewModelModule::class,
PreferencesModule::class,
ActivityBindingModule::class
]
)
interface AppComponent: AndroidInjector<MyApp> {
#Component.Builder
abstract class Builder: AndroidInjector.Builder<MyApp>()
}
And here's the code for the ActivityBindingModule, responsible for setting up the subcomponents (in this case, activities used by my app):
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector()
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector
internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Internally, each activity is instantiating the view model with code that looks like this (called from within the onCreate method):
//view model code
_viewModel = ViewModelProviders.of(this, viewModelFactory)[ShowDetailsViewModel::class.java]
And, as you'd expect, viewModelFactory is injected as field:
#Inject lateinit var viewModelFactory: ViewModelProvider.Factory
Both view models have external dependencies which are set up on the other modules referenced by the top app component.
And, for the sake of completeness, here's the code for my view model factory:
#Singleton
class ViewModelFactory #Inject constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T
= viewModels[modelClass]?.get() as T
This code works, but it seems like it can be improved. After reading the docs, I'm under the impression that I could refactor my ViewModeModule so that it will simply instantiate my ViewModelFactory and move each of the view model declarations into separate module (so that each of them can be injected only in the "correct" activity).
In order to test this, I've started by moving the ShowDetailsViewModel into a new module which has only one entry:
#Module
internal abstract class DetailsModule {
#Binds
#IntoMap
#ViewModelKey(ShowDetailsViewModel::class)
abstract fun bindShowDetaislViewModel(viewModel: ShowDetailsViewModel): ViewModel
}
After that, the ViewModelModule looks like this:
#Module
abstract class ViewModelModule {
#Binds
internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
#Binds
#IntoMap
#ViewModelKey(MainActivityViewModel::class)
internal abstract fun bindMainActivityViewModel(viewModel: MainActivityViewModel): ViewModel
}
And I've updated the ActivityBindingModule so that in looks like this:
#Module
abstract class ActivityBindingModule {
#ActivityScoped
#ContributesAndroidInjector()
internal abstract fun mainActivity(): MainActivity
#ActivityScoped
#ContributesAndroidInjector(modules = [DetailsModule::class])
internal abstract fun showDetailsActivity(): ShowDetailsActivity
}
Notice that now I'm passing the DetailsModule (which instantiates the ShowDetailsViewModel) to the ContributeAndroidInjector annotation which is applied to the showDetailsActivity method because that view model is only used by that activity.
Now, I'm surely missing something because after doing this, I'm always getting the following exception:
java.lang.IllegalStateException: ViewModelProviders.of(th…ilsViewModel::class.java] must not be null
If I debug the app, I can see that moving the ShowDetailsViewModel into its own model does not register it on the map used by the factory (ie, the map has only one entry, which corresponds to the MainActivityViewModel that is registered in the ViewModelModule.
I thought that moving each view model the declaration into each a module used by a subcomponent should still allow it to be registered in a map injected by a module which is registered with the top component. Am I wrong? What is it that I'm missing to make this work?
Thanks.
The problem lies with ViewModelFactory being #Singleton and that it won't get any of the bindings you add in your subcomponents. Remove the scope from your factory or make it #ActivityScoped (the same scope as the ViewModel for the Activity)
The Activity (#ActivityScoped) has access to the factory (#Singleton), but the factory (#Singleton) does not have access to use or create the ViewModel from a lower scope (#ActivityScoped). So moving the factory to the same scope (#ActivityScoped) would give it access to create the viewmodel in question.
I'm trying to create UserScope with https://google.github.io/dagger/android I have #Singleton, #ActivityScope, #FragmentScope and #UserScope.
AppComponent
#Singleton
#Component(
modules = [
AndroidSupportInjectionModule::class,
ActivityBindingModule::class,
AppModule::class,
ApiModule::class
]
)
interface AppComponent {
fun inject(application: Application)
fun createUserComponent(): UserComponent.Builder
#Component.Builder
interface Builder {
#BindsInstance
fun application(app: Application): Builder
fun apiModule(module: ApiModule): Builder
fun build(): AppComponent
}
}
UserComponent:
#UserScope
#Subcomponent(
modules = [UserModule::class]
)
interface UserComponent {
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun user(user: User): Builder
fun build(): UserComponent
}
}
UserModule:
#Module
class UserModule {
#UserScope
#Provides
fun provideUser(user: User): User = user
}
Here is how I am creating UserComponent after successfull login:
private fun createSession(user: User) {
userComponent = appComponent.createUserComponent().user(user).build()
}
And also I have UserManager which is triggering an issue with User injection constructor param
#UserScope
class SomeManager #Inject constructor(
private val apiService: ApiService,
private val user: User
) {}
Error message: Error:[dagger.android.AndroidInjector.inject(T)]
domain.model.authorize.User cannot be provided without an #Inject
constructor or from an #Provides-annotated method.
di.modules.MainActivityBindingModule_ContributeUserFragment.UserFragmentSubcomponent
scoped with #di.scopes.FragmentScope may not reference bindings with
different scopes: #di.scopes.UserScope class
domain.managers.SomeManager
I want to create #UserScope to manage related ApiManagers lifecycle
UPD
#Module
class UserFragmentModule {
#Provides
#FragmentScope
fun provideViewModelFactory(someModule: SomeModule) = UserFragmentViewModelFactory(someModule)
}
ContributesAndroidInjector logic:
#Module
interface ActivityBindingModule {
#ActivityScope
#ContributesAndroidInjector(modules = [SplashModule::class])
fun contributeSplashActivity(): SplashActivity
#ActivityScope
#ContributesAndroidInjector(modules = [SignInModule::class])
fun contributeAuthorizeActivity(): Activity
#ActivityScope
#ContributesAndroidInjector(modules = [MainModule::class])
fun contributeMainActivity(): MainActivity
}
#Module(includes = [MainActivityBindingModule::class])
class MainModule
#Module
interface MainActivityBindingModule {
#FragmentScope
#ContributesAndroidInjector(modules = [UserFragmentModule::class])
fun contributeUserFragment(): UserFragment
}
You are trying to inject a User instance that is provided only in the #UserScope in #FragmentScope.
Basically, the rule is whenever you need a User injected - you need to be in the #UserScope.
You need to change your #FragmentScope to #UserScope annotation in all places like that.
From what you posted I believe it is here:
#Module
class UserFragmentModule {
#Provides
#UserScope
fun provideViewModelFactory(someModule: SomeModule) = UserFragmentViewModelFactory(someModule)
}
And you'll need to move your UserFragmentModule to the UserComponent:
#UserScope
#Subcomponent(
modules = [UserModule::class, UserFragmentModule::class]
)
interface UserComponent {
And you'll also need to inject with application.userComponent into your UserFragment
Here is a good article about subcomponents. I'd recommend you to read it to gain full understanding.
Another option, I'm not sure about this one, but maybe you can just inject your UserFragment with your application.userComponent.
I think your problem is wrong use of scopes.
Scopes help us to handle injected item life-cycle to prevent keep un-needed object in whole app life cycle.
and take look at this image:
Your modules should be in same scope that your component is.
for example in your AppComponent, you have ActivityBindingModule that have ActivityScope. why you do this?
I think it is better to have a activityComopnent that have dependency toappComponent. If your activityComponent had some dependency in upper scoped component (appComponent), first you had add dependency of that component. (dependencies= [ AppComponent::class]). Second, you had to expose needed dependency in appComponent with a method that return needed dependency object. Finally in wireUpping you should call appComponent as call activity modules in Dagger wire upping. ( I will show this in a example)
So all you need is activityComponent like this:
#ActivityScope
#Component(modules = [ActivityBindingModule::class],dependencies= [ AppComponent::class])
interface activityComponent {
.
.
.
This is my example written in java that shows how connect appComponent and activityComponent:
#Singleton
#Component(modules = {ApplicationModule.class ,ServicesModule.class})
public interface ApplicationComponent {
void inject(ImicoApplication imicoApplication);
// exposed for child-components.
Context getContext();
Application getApplication();
CompanyService getCompanyService();
CategoryService getCategoryService();
RatingService getRatingService();
NewsService getNewsService();
AuthorizationManager getAuthorizationManager();
}
And this is activityComponent:
#ActivityScope
#Component(dependencies = {ApplicationComponent.class},modules = {SearchActivityModule.class})
public interface SearchserviceComponent {
void inject (SearchFragment searchFragment);
}
And in SearchFragment do this for wire-up:
DaggerSearchserviceComponent.builder()
.applicationComponent(((ImicoApplication) getActivity().getApplication()).getApplicationComponent())
.searchActivityModule(new SearchActivityModule(this)).build().inject(this);
If in SearchFragment i need CompanyService just inject it and it is provided and exposed by applicationComponent.
I have an app component and a dependent component. The app component declares explicit dependencies, and the dependent component can inject those. However, when I have a dependency that I have to disambiguate with a #Qualifier, the dependent component is not able to inject that dependency.
This is the app component
#Component(modules = [AppModule::class, SchedulersModule::class, StorageModule::class])
#ApplicationScope
interface AppComponent {
fun inject(app: Application)
/* other stuff omitted for brevity */
val bitmapCache: BitmapCache
#UiScheduler fun uiScheduler(): Scheduler
}
This is the scheduler module:
#Module
class SchedulersModule {
#ApplicationScope
#Provides
#IoScheduler
fun provideIoScheduler(): Scheduler = Schedulers.io()
#ApplicationScope
#Provides
#UiScheduler
fun provideMainThreadScheduler(): Scheduler = AndroidSchedulers.mainThread()
}
This is the qualifier:
#Qualifier
#Retention(AnnotationRetention.RUNTIME)
annotation class UiScheduler
And this is the dependent component:
#Component(
dependencies = [AppComponent::class],
modules = [EditEntryActivityModule::class, ViewModelModule::class]
)
#ActivityScope
interface EditEntryActivityComponent {
fun inject(editEntryActivity: EditEntryActivity)
fun inject(editEntryFragment: EditEntryFragment)
}
This is how the scheduler is injected in the fragment:
class EditEntryFragment : Fragment() {
#Inject #UiScheduler lateinit var uiScheduler: Scheduler
/* other stuff */
}
So why can the dependent component inject the bitmap cache, declared in the parent component, but not the UI scheduler? This is the error I get:
error: io.reactivex.Scheduler cannot be provided without an #Provides- or #Produces-annotated method.
io.reactivex.Scheduler is injected at
com.test.edit.EditEntryFragment.uiScheduler
com.test.edit.EditEntryFragment is injected at
com.test.edit.EditEntryActivityComponent.inject(arg0)
1 error
Using #field:UiScheduler in class EditEntryFragment
Try #Named annotition
#Inject #field:Named("UiScheduler") lateinit var uiScheduler: Scheduler
check out this issue