I am trying to use #BindsInstance in a Component Builder, but can't inject dependency into a module
That is the component:
#Component(modules = [MainModule::class])
interface MainComponent {
fun inject(activity: MainActivity)
#Component.Builder
interface Builder {
#BindsInstance
fun mainDep(dep: MainTest): Builder
fun build(): MainComponent
}
}
That is the module:
#Module
class MainModule {
#Provides
fun provideMainTest(mainTest: MainTest) = mainTest
}
That is the class to inject:
interface MainTest {
fun mainTest()
}
class MainTestImpl: MainTest {
override fun mainTest() {
println("main test")
}
}
But when I try to inject it into MainActivity I get a compiler error
error: [Dagger/DependencyCycle] Found a dependency cycle:
public abstract interface MainComponent {
^
com.example.testdagger.di.MainTest is injected at
com.example.testdagger.di.MainModule.provideMainTest(mainTest)
com.example.testdagger.di.MainTest is injected at
com.example.testdagger.di.MainModule.provideMainTest(mainTest)
Can you try and say what will happen like this:
#BindsInstance
fun mainDep(dep: MainTestImpl): Builder
and also:
#Module
class MainModule {
#Provides
fun provideMainTest(mainTestImpl: MainTestImpl): MainTest = mainTestImpl
}
I know that it is not exactly what you want to achieve, but I want to check the result. Also, please provide how you create the component and how do you inject the Activity.
Related
I'm planning to create Espresso tests on my app multi-module, and I'm about to create the first Espresso test, but what I'm seeing is that on my app I do not have an AppComponent where I can fake it. Since I want to add the test on my feature-module, I'll create the TestApp, TestRunner there from now.
What I have on my feature-module is a FeatureComponent that is injected via ComponentFactory from the App, so what I thought is to create a class like this :
#Component (
dependencies = [ MoreComponents::class],
modules = [ DataSourceModule::class ]
)
interface FeatureOneComponent {
fun activityOneSubComponent(): FeatureOneActivity.Component.Factory
fun activityTwoSubComponent(): FeatureTwoActivity.Component.Factory
#Component.Factory
interface Factory {
fun create(
dependencies
):FeatureOneComponent
}
}
interface FeatureOneProvider {
fun getFeatureOneComponent(): FeatureOneComponent
}
///ACTIVITY
class FeatureOneActivity : AppCompatActivity() {
//this comes from Subcomponent is what I want to MOCK
#Inject lateinit var presenter
//these comes from the factory and I have it mocked
#Inject lateinit var manager
override fun onCreate(){
(applicationContext as FeatureOneProvider).getFeatureOneComponent().activityOneSubComponent().create(this).inject(this)
}
}
#Subcomponent(modules = [ActivityOneModule::class]) <--- THIS I WANT TO MOCK
interface Component {
fun inject(activity: FeatureOneActivity)
#SubComponent.Factory
interface Factory {
fun create(#BindsInstance activity: FeatureOneActivity): Component
}
}
#Module
interface ActivityOneModule {
#Binds
fun bindPresenter(impl: PresenterImpl): Contract.Presenter
}
TEST
class MyTestApp : Application(), FeatureOneProvider {
override fun getFeatureOneComponent(): FeatureOneComponent {
return DaggerMockFeatureOneComponent.create()
}
}
#Component(
modules = [MockFeatureOneModules::class]
)
interface MockFeatureOneComponent : FeatureOneComponent {
//I NEED TO MOCK THE SUBCOMPONENT WITH `MockSubcomponent`
}
#Component
object MockFeatureOneModules {
#Provides
fun providesManager() : MyManager = mock(MyManager)
}
//I want to use this module to replace the subcomponent of my activity
#Module
object MockSubcomponent() {
#Provides
fun providesFakePresenter() : FeatureOneContract.Presenter = mock { FeatureOneContract.Presenter::class.java }
}
To better understand the problem
When I run my test and I put a debugger point I see everything is mocked but the Presenter, and that's because the presenter is in
#Subcomponent(modules = [ActivityOneModule::class]
interface Component {
fun inject(activity: FeatureOneActivity)
#SubComponent.Factory
interface Factory {
fun create(#BindsInstance activity: FeatureOneActivity): Component
}
}
#Module
interface ActivityOneModule {
#Binds
fun bindPresenter(impl: PresenterImpl): Contract.Presenter
}
And in my test component I don't have access to "override" this subcomponent so everything is mocked but this subcomponent and I need this mocked.
I don't know if that's the best idea but if I did not misunderstand you you want this Presenter to return a mock {}. The changes you could do are :
In your TestComponent change interface to abstract class
Duplicate your subcomponent and extends from the real one
#Component(
modules = [MockFeatureOneModules::class]
)
abstract class MockFeatureOneComponent : FeatureOneComponent {
abstract fun subcomponent() : MockComponent.FactoryMock
override fun activityOneSubComponent(): FeatureOneActivity.Component.Factory {
return subcomponent()
}
#Subcomponent
interface MockComponent : FeatureOneActivity.Component {
#Subcomponent.Factory
interface FactoryMock : FeatureOneActivity.Component.Factory {
override fun create....
}
}
}
And that's it, it should work.
Your example code is pretty complex to understand and the actual problem.
But what I understand you want to setup expresso test for your feature module and you need to setup dagger component for it.
So, I can give you some guidelines and example code so that you can follow and setup your dagger architecture for your espresso test very simply.
First of all, you need setup/create your App for espresso test like this:
class MyTestApplication : MyApplication() {
//Call this from MyApplication onCreate()
override fun initDaggerGraph() {
component = DaggerTestAppComponent.builder()
.application(this)
.appLifecycle(appLifecycle)
.build()
component.inject(this)
}
}
Then create your Test app component like this:
//Add all of your dependent modules in this TestAppModule
#Component(modules = [TestAppModule::class])
interface TestAppComponent : AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
#BindsInstance
fun appLifecycle(appLifecycle: AppLifecycle): Builder
fun build(): TestAppComponent
}
fun inject(activityTest: SomeActivityTest) //Your activity where to inject
}
Also, make sure to initialize your component in your Test activity class when launching the activity like this:
val component = MyApplication.instance.component as TestAppComponent
component.inject(this)
Now you have done all the setup and your dependency should resolve as well as your espresso test should work.
So as far as i get it, you have multiple modules, components and subcomponents, but since most of their names don't quite match up in the code you posted, nor you posted the error log, i have to guess whats going wrong where.
Instead, how about something like this.
public interface SomeComponent {
WhateverClass1 provideWhatever1();
WhateverClass2 provideWhatever2();
WhateverRepository1 provideWhateverRepository1();
SomeOtherComponent getSomeOtherComponent();
// and so on and on
}
and then have your production component look something like this:
#SomeComponentSingleton
#Component(modules = { ... },
dependencies = { ... })
public interface SomeProductionScopedComponent extends SomeComponent {
#Component.Builder
interface Builder {
// you know best what needs to go here
SomeProductionScopedComponent build();
}
}
and this
#SomeComponentSingleton
#Component(dependencies = SomeComponent.class, modules =
{ ... })
public interface TestSomeComponent {
#Component.Builder
interface Builder {
Builder bindCoreComponent(SomeComponent coreComponent);
#BindsInstance
Builder bindContext(Context context);
TestSomeComponent build();
}
}
then, given that you're somewhat manually instantiating the components with these Builders, as long as they depend on the interface (SomeComponent), you should be able to bind a ProductionSomeComponent or a TestSomeComponent into your module.
Makes sense or gives some hint?
I'm learning dagger2 with a module architecture. And I think, something is not clear to me, for example
in module utilites i have di package
class UtilsComponent
#Component(modules = [UtilsModule::class])
interface UtilsComponent {
fun getResourceProvider() : IResourceProvider
fun getNetworkProvider(): INetworkProvider
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): UtilsComponent.Builder
fun build(): UtilsComponent
}
}
#Module
abstract class UtilsModule {
#Binds
abstract fun bindContext(application: Application): Context
#Module
companion object {
#Provides
#JvmStatic
fun bindResourceProvider(context: Context): IResourceProvider {
return ResourceProvider(context = context)
}
#Provides
#JvmStatic
fun bindNetworkProvider(context: Context): INetworkProvider {
return NetworkProvider(context = context)
}
}
}
then in app package in AppComponent i included all modules
#Component(
dependencies = [UtilsComponent::class],
modules = [
AndroidInjectionModule::class,
ActivityBindingModule::class,
MainModule::class // test module
]
)
#AppScope
interface AppComponent: AndroidInjector<App> {
// inject to ...
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): AppComponent.Builder
fun utilsComponent(utilsComponent: UtilsComponent): AppComponent.Builder
fun build(): AppComponent
}
}
In the app component, I have MainModule - this is my test module, so this module looks like this
#Module
class MainModule {
#Provides
fun getMainPresenter(networkProvider: NetworkProvider): MainPresenter {
return MainPresenter(networkProvider)
}
}
when I run the app, I have an error
[Dagger/MissingBinding] com.example.utilities.di.UtilsModule cannot be
provided without an #Provides-annotated method. public abstract
interface AppComponent extends
dagger.android.AndroidInjector<com.example.testmoduleapp.App> {
^
com.example.utilities.di.UtilsModule is injected at
com.example.testmoduleapp.di.modules.MainModule.getMainPresenter(utilsModule)
com.example.testmoduleapp.ui.activities.main.MainPresenter is injected at
com.example.testmoduleapp.ui.activities.main.MainActivity.mainPresenter
com.example.testmoduleapp.ui.activities.main.MainActivity is injected at
dagger.android.AndroidInjector.inject(T) [com.example.testmoduleapp.di.AppComponent →
com.example.testmoduleapp.di.modules.ActivityBindingModule_MainActivity.MainActivitySubcomponent]
I understand that the error is because I have not a module witch return NetworkProvider object, but I can't understand how I can get this object from UtilsModule
also in App
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector
companion object{
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
initializeDagger()
}
private fun initializeDagger() {
appComponent = DaggerAppComponent
.builder()
.application(this)
.utilsComponent(provideUtilsComponent())
.build()
}
private fun provideUtilsComponent(): UtilsComponent {
return DaggerUtilsComponent
.builder()
.application(this)
.build()
}
In Dependencies Graph You already provide INetworkProvider so when you need NetworkProvider it means Dagger can not be resolved. Change to this, don't forget to change constructor MainPresenter to INetworkProvider
#Provides
fun getMainPresenter(networkProvider: INetworkProvider): MainPresenter {
return MainPresenter(networkProvider)
}
but i cant understand how i can get this object from UtilsModule
For your question, in UtilComponent you already exposed getNetworkProvider() it means any Componenent dependencies to UtilsComponent can be get it.
My AppModule crashes in compile time with error:
error: .App cannot be provided without an #Inject constructor or from an #Provides-annotated method.
public abstract .vcs.IGitHubApi getGitHubApi();
^
.App is injected at
.AppModule.provideOAuth2Interceptor(app)
.vcs.OAuth2Interceptor is injected at
.AppModule.provideOkHttpClient(…, oAuth2Interceptor)
okhttp3.OkHttpClient is injected at
.AppModule.provideRetrofit(httpClient, …)
retrofit2.Retrofit is injected at
.AppModule.provideGitHubApi(retrofit)
.vcs.IGitHubApi is provided at
.AppComponent.getGitHubApi()
Here is my AppModule class:
#Module
class AppModule {
// other providers
#Singleton
#Provides
fun provideOAuth2Interceptor(app: App): OAuth2Interceptor {
return OAuth2Interceptor(app)
}
}
AppComponent:
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent {
// other methods
fun inject(app: App)
#Component.Builder
interface Builder {
#BindsInstance
fun context(context: Context): Builder
fun build(): AppComponent
}
}
And my App class where I initialize AppComponent:
class App: Application() {
override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.context(this)
.build()
.inject(this)
}
}
I get that Dagger can't find App to build provideOAuth2Interceptor but I don't know how to inject App in provider.
P.S. I am learning Dagger still.
In your AppComponent, you should bind the instance of your App class to make it a part of Dagger graph.
#Component.Builder
interface Builder {
#BindsInstance
fun context(context: Context): Builder
#BindsInstance
fun application(app: App): Builder
fun build(): AppComponent
}
and in your App class, provide the instance of App to the component while construction-
DaggerAppComponent.builder()
.context(this)
.application(this)
.build()
.inject(this)
I decided to learn dagger dependency injection framework. After some tutorials I try to implement dagger into my project. However I got this error
com\assigment\di\component\AppComponent.java:11: error: [Dagger/MissingBinding] java.util.Map<java.lang.Class<? extends android.app.Activity>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.app.Activity>>> cannot be provided without an #Provides-annotated method.
public abstract void inject(#org.jetbrains.annotations.NotNull()
^
java.util.Map<java.lang.Class<? extends android.app.Activity>,javax.inject.Provider<dagger.android.AndroidInjector.Factory<? extends android.app.Activity>>> is injected at
dagger.android.DispatchingAndroidInjector.<init>(injectorFactories)
dagger.android.DispatchingAndroidInjector<android.app.Activity> is injected at
assigment.com.assigment.App.activityInjector
assigment.com.assigment.App is injected at
assigment.com.assigment.di.component.AppComponent.inject(assigment.com.assigment.App)
I try to solve this for 2 days but with no luck. So here's how I set up my project
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent
.builder()
.appModule(AppModule())
.build()
}
override fun activityInjector(): AndroidInjector<Activity> {
return activityInjector
}
}
Here's my app component
#Singleton
#Component(modules = [AppModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
and here's my appModule class
AppModule
#Module
class AppModule {
private val url = "http://test.lt/v1/"
#Provides
#Singleton
fun provideApplication(app: Application): Context = app
}
So what I'm missing with this implementation?
Add AndroidInjectionModule.class and ActivityBuilder.class to your AppComponent
#Singleton
#Component(modules = [AndroidInjectionModule::class, AppModule::class, ActivityBuilder::class ])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
You have two problems.
To get rid of the compiler error add AndroidInjectionModule::class to Component modules:
#Singleton
#Component(modules = [AndroidInjectionModule::class, AppModule::class])
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
override fun inject(app: App)
}
With #Component.Builder annotated interface you define the builder interface with an annotated #BindsInstance method application() (note that there is not an appModule method declared in the Builder).
With such declaration you can build your component using application(this):
appComponent = DaggerAppComponent
.builder()
.application(this)
.build()
In this way this application instance is bound inside the component.
Just as a side note: Binding Instances are documented here, but personally I found the explanation quite hard to grasp for someone learning dagger, like me.
Detail answer with explanation
Component - >
Component is a graph. Component will provide injected instances by using modules.
#Component(
modules = [
AndroidInjectionModule::class, //We didn’t create this. It is an internal class in Dagger 2.10. Provides our activities and fragments with given module
ActivityModule::class,
]
)
#Singleton
interface AppComponent : AndroidInjector<App> {
#Component.Builder
interface Builder {
fun addContext(#BindsInstance context: Context): Builder
fun build(): AppComponent
}
}
We created ActivityModule module. This is a given module to dagger. We map all our activities here. And Dagger know our activities in compile time. In our app we have MainActivity. So we map it here.
#Module
abstract class ActivityModule {
#ContributesAndroidInjector
public abstract MainActivity bindMainActivity();
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<application
android:name=".App"
App.kt
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().addContext(this).build()
}
}
And Make sure to extend your activity with DaggerAppCompatActivity that will auto inject before onCreate
Hi is it possible to add test modules in my AppComponent?
Below is my real representation of my appComponent
#Singleton
#Component(modules = arrayOf(MainModule::class,
AnalyticsModule::class,
MainAndroidBinding::class,
AccountAndroidBinding::class,
AndroidSupportInjectionModule::class,
HomeAndroidBinding::class,
NetworkModule::class))
interface ApplicationComponent : AndroidInjector<DaggerApplication> {
fun inject(myApplication: MyApplication)
override fun inject(instance: DaggerApplication)
#Component.Builder
interface Builder {
#BindsInstance
fun application(applicaton: Application): Builder
fun build(): ApplicationComponent
}
}
I could just add the test modules directly to the testAppComponent like this but it doesnt offer me much flexibility to dynamically add different testModules.
#Singleton
#Component(modules = [
(MainModuleTest::class),
(TestMainAndroidBindingTest::class),
(AccountAndroidBindingTest::class),
(AnalyticsModuleTest::class),
(AndroidSupportInjectionModule::class),
(NetworkModuleTest::class)])
interface TestAppComponent : ApplicationComponent {
fun inject(launchActivityTest: LaunchActivityTest)
#Component.Builder
interface Builder {
#BindsInstance
fun application(applicaton: Application): Builder
fun build(): TestAppComponent
}
}
Here is my MyApplication class
class MyApplication : DaggerApplication() {
companion object {
private lateinit var application: MyApplication
fun get(): MyApplication {
return application
}
}
#Inject
lateinit var dispatchingActivityInjector: DispatchingAndroidInjector<Activity>
lateinit var applicationComponent: ApplicationComponent
override fun onCreate() {
super.onCreate()
application = this
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
applicationComponent = DaggerApplicationComponent.builder()
.application(this)
.build()
applicationComponent.inject(this)
return applicationComponent
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
On the LaunchActivityTest this is how i set it up to use this testApp component
#Before
fun setUp() {
val app = InstrumentationRegistry.getTargetContext().applicationContext as MyApplication
val testAppComponent = DaggerTestAppComponent.builder().application(app).build()
app.applicationComponent = testAppComponent
testAppComponent.inject(this)
}
I was following this guide until i stumbled o the point where my DaggerTestAppComponent doesnt expose the modules for me to dynamically add myself due to the fact that my AppComponent class extends AndroidInjector which automatically adds the modules for you
https://proandroiddev.com/writing-espresso-instrumentation-tests-with-dagger2-kotlin-d30f12c4769b
The above dynamically adds its modules like this:
testAppComponent = DaggerTestAppComponent.builder()
.appModule(AppModule(app))
.apiModule(TestApiModule())
.prefModule(TestPrefModule())
.build()
I cant do that in my case unless i redo my AppComponent so that it doesnt extend AndroidInjector. If i do that then in my real impl code i have to manually set the modules.
Is there any other way?
You should add a function to your component builder and use "BindsInstance".
Example component
#Singleton
#Component(modules = {
AndroidSupportInjectionModule.class,
ApplicationTestModule.class,
ActivityBuilder.class})
public interface TestExampleComponent extends AndroidInjector<DaggerApplication> {
void inject(TestApplication app);
#Override
void inject(DaggerApplication instance);
#Component.Builder
interface Builder {
#BindsInstance
TestExampleComponent.Builder application(DaggerApplication application);
Builder applicationModule(ApplicationTestModule appTestModule);
TestExampleComponent build();
}
}
In this component, I added applicationModule function with using "BindsInstance" and I can pass ApplicationTestModule.
Then you can add different test modules.
TestApplicationComponent appComponent = DaggerTestAppComponent.builder().application(this).
applicationModule(appTestModule).build();
appComponent.inject(this);