I'm trying to find the correct way to override a Component or Module within a Espresso test while using latest Dagger2 #Component.Factory features.
I have the following component definition:
#Component(
modules = [SomeModule::class],
dependencies = [SessionComponent::class]
)
interface MainComponent {
#Component.Factory
interface Factory {
fun create(sessionComponent: SessionComponent): MainComponent
}
}
and SomeModule definition it's:
#Module
object SomeModule {
#Provides
#JvmStatic
fun some(): Some = SomeImpl()
}
Some it's an interface and SomeImpl will be it's implementation.
Now, to inject in our activity, we just do:
DaggerMainComponent.factory().create(sessionComponent).inject(this)
We resolve the sessionComponent with and extension function declared in our CustomApp: Application
val Context.sessionComponent: SessionComponent
get() = App.sessionComponent(this)
Now as per recommendations in the official guide: https://dagger.dev/testing we should try to replace MainComponent with a TestMainComponent which will have a TestSomeModule with our test implementation.
My initial thoughts were in this direction:
Remove the injection from the activity and replaced it by an Injector object that will do it. Also, modify the create() method and pass the Module:
DaggerMainComponent.factory().create(sessionComponent).inject(this)
It's moved to:
object Injector {
#VisibleForTesting
var someModule: SomeModule = SomeModule()
fun inject(activity: Activity) {
when (activity) {
is MainActivity -> {
DaggerMainComponent.factory().create(App.sessionComponent(activity), someModule).inject(activity)
}
}
}
}
And the new Factory definition:
#Component.Factory
interface Factory {
fun create(sessionComponent: SessionComponent, someModule: SomeModule): MainComponent
}
Then, after the onCreate() in the activity we just invoke Injector.inject(this)
The idea behind this was that in a UI Test we could do:
#Before
fun setup() {
Injector.someModule = TestSomeModule
}
and change the implementation that way. It fails thought since SomeModule it's a singleton object and we cannot extend from it.
This lead me to the following approach:
The app has release and debug buildTypes. Within the release and debug folder I have the real implementation of the Injector (with MainComponent referencing SomeModule).
Then in the androidTest folder I have another Injector object but with the following:
object Injector {
fun inject(activity: Activity) {
when (activity) {
is MainActivity -> {
DaggerTestMainComponent.factory().create(App.sessionComponent(activity)).inject(activity)
}
}
}
}
and TestMainComponent it's
#Component(
modules = [TestSomeModule::class],
dependencies = [SessionComponent::class]
)
interface TestMainComponent {
#Component.Factory
interface Factory {
fun create(sessionComponent: SessionComponent): TestMainComponent
}
}
#Module
object TestSomeModule {
#Provides
#JvmStatic
fun greeter(): Some = TestSomeImpl()
}
This approach works and gradle replaces the Injector object from the debug folder with the one in the androidTest folder, however, I'm wondering if there's a better approach to avoid having to use two Injector objects for this purpose, or maybe just update the Module used with the test one.
Any advice it's welcome!
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?
Background
I am trying to use dagger in a multi module setup. One of my aim is to reduce the number of components being used. So basically aiming for 1 component per feature module.
Setup core->app->feature
Problem
Dagger fails with the exception A binding with matching key exists in component: which refers to that I have bound a dependency somewhere in my entire object graph but it cannot be reached.
But for my scenario I am creating the sub-component in my activity and calling inject to make sure the component has the access to my activity. This atleast in my understanding should be accessible but it's still not able to provide the dependency of my viewmodel.
Here is the sample/multi-module in case someone wants to try out.
Stacktrace
/Users/feature1/build/**/FeatureComponent.java:8: error: [Dagger/MissingBinding]
com.**.FeatureActivity cannot be provided without an #Inject constructor or an #Provides-annotated
method. This type supports members injection but cannot be implicitly provided.
public abstract interface FeatureComponent {
^
A binding with matching key exists in component: com.**.FeatureComponent
com.**.FeatureActivity is injected at
com.**.FeatureModule.provideVM(activity)
com.**.FeatureViewModel is injected at
com.**.FeatureActivity.vm
com.**.FeatureActivity is injected at
com.**.FeatureComponent.inject(com.**.FeatureActivity)
AppComponent
#AppScope
#Component(dependencies = [CoreComponent::class])
interface AppComponent {
fun inject(app: MainApp)
#Component.Factory
interface Factory {
fun create(
coreComponent: CoreComponent
): AppComponent
}
}
CoreComponent
#Singleton
#Component
interface CoreComponent {
fun providerContext(): Context
#Component.Factory
interface Factory {
fun create(
#BindsInstance applicationContext: Context
): CoreComponent
}
}
FeatureComponent
#Component(
modules = [FeatureModule::class],
dependencies = [CoreComponent::class]
)
#FeatureScope
interface FeatureComponent {
// Planning to use this component as a target dependency for the module.
fun inject(activity: FeatureActivity)
}
Feature Module
#Module
class FeatureModule {
#Provides
fun provideVM(activity: FeatureActivity): FeatureViewModel {
val vm by activity.scopedComponent {
FeatureViewModel()
}
return vm
}
}
Feature VM
class FeatureViewModel #Inject constructor(): ViewModel()
Since I'm using activity to provide my viewModel I will have to use #BindsInstance to bind the instance of any activity or fragment that I want to inject.
In short if I change my feature component to the following code it starts to work where I bind the instance of the activity at the creation of the component.
PS: If anyone knows a better to inject the fragment at later stage with just using one component, please feel free to improve this answer.
FeatureComponent
#Component(
modules = [FeatureModule::class],
dependencies = [CoreComponent::class]
)
#FeatureScope
interface FeatureComponent {
fun inject(activity: FeatureActivity)
#Component.Factory
interface Factory {
fun create(
#BindsInstance applicationContext: FeatureActivity,
coreComponent: CoreComponent,
): FeatureComponent
}
}
I'm trying to create Espresso tests and using a mockWebServer the thing is when I try to create my mockWebServer it calls the real api call and I want to intercept it and mock the response.
My dagger organisation is :
My App
open class App : Application(), HasAndroidInjector {
lateinit var application: Application
#Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector
override fun onCreate() {
super.onCreate()
DaggerAppComponent.factory()
.create(this)
.inject(this)
this.application = this
}
}
Then MyAppComponent
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
RetrofitModule::class,
RoomModule::class,
AppFeaturesModule::class
]
)
interface AppComponent : AndroidInjector<App> {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: App): AppComponent
}
}
Then I've created this TestApp
class TestApp : App() {
override fun androidInjector(): AndroidInjector<Any> = androidInjector
override fun onCreate() {
DaggerTestAppComponent.factory()
.create(this)
.inject(this)
}
}
And this is my TestAppComponent
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
TestRetrofitModule::class,
AppFeaturesModule::class,
RoomModule::class]
)
interface TestAppComponent : AppComponent {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: App): TestAppComponent
}
}
Note: Here I've created a new module, called TestRetrofitModule where the BASE_URL is "http://localhost:8080", I don't know if I need something else.
Also I've created the TestRunner
class TestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, TestApp::class.java.name, context)
}
}
And put it on the testInstrumentationRunner
Problem 1
I can not use
#Inject
lateinit var okHttpClient: OkHttpClient
because it says that it's not initialised.
Problem 2 (Solved thanks Skizo)
My mockWebServer is not dispatching the responses even-though is not pointing the real api call, is pointing the one that I've put to the TestRetrofitModule, the thing is that I have to link that mockWebServer and Retrofit.
The setup you posted looks correct. As for App not being provided, you probably need to bind it in your component, since right now you're binding TestApp only. So you need to replace
fun create(#BindsInstance application: TestApp): TestAppComponent
with
fun create(#BindsInstance application: App): TestAppComponent
I had the same problem with mockWebServer recently, what you need to do is to put a breakpoint and see what's the error, in my case I put it on my BaseRepository where I was doing the call, and found that the exception was :
java.net.UnknownServiceException: CLEARTEXT communication to localhost not permitted by network security policy
What I did to solve the problem is add this on my manifest.xml
android:usesCleartextTraffic="true"
But you may have to use other approaches you can take a look on android-8-cleartext-http-traffic-not-permitted.
When I try to do something similar, I don't create two types of application-components, just one. I provide them with different inputs, based on whether it's for the actual App or for the TestApp. No need for TestAppComponent at all. E.g.
open class App : Application(), HasAndroidInjector {
lateinit var application: Application
#Inject
lateinit var androidInjector: DispatchingAndroidInjector<Any>
override fun androidInjector(): AndroidInjector<Any> = androidInjector
override fun onCreate() {
super.onCreate()
DaggerAppComponent.factory()
.create(this, createRetrofitModule())
.inject(this)
this.application = this
}
protected fun createRetrofitModule() = RetrofitModule(BuildConfig.BASE_URL)
}
class TestApp : App() {
override fun createRetrofitModule() = RetrofitModule("http://localhost:8080")
}
#Module
class RetrofitModule(private val baseUrl: String) {
...
provide your Retrofit and OkHttpClients here and use the 'baseUrl'.
...
}
(not sure if this 'compiles' or not; I usually use the builder() pattern on a dagger-component, not the factory() pattern, but you get the idea).
The pattern here is to provide your app-component or its modules with inputs for the 'edge-of-the-world', stuff that needs to be configured differently based on the context in which the app would run (contexts such as build-flavors, app running on consumer device vs running in instrumentation mode, etc). Examples are BuildConfig values (such as base-urls for networking), interface-implementations to real or fake hardware, interfaces to 3rd party libs, etc.
How about a dagger module for your Test Class with a ContributeAndroidInjector in there and do Inject on a #Before method.
Your TestAppComponent:
#Component(modules = [AndroidInjectionModule::class, TestAppModule::class])
interface TestAppComponent {
fun inject(app: TestApp)
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: TestApp): Builder
fun build(): TestAppComponent
}
}
TestAppModule like:
#Module
interface TestAppModule {
#ContributesAndroidInjector(modules = [Provider::class])
fun activity(): MainActivity
#Module
object Provider {
#Provides
#JvmStatic
fun provideString(): String = "This is test."
}
// Your other dependencies here
}
And #Before method of Test Class you must be do:
#Before
fun setUp() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val app = instrumentation.targetContext.applicationContext as TestApp
DaggerTestAppComponent.builder().application(app).build().inject(app)
// Some things other
}
An important thing, you must be have (on build.gradle module app):
kaptAndroidTest "com.google.dagger:dagger-compiler:$version_dagger"
kaptAndroidTest "com.google.dagger:dagger-android-processor:$version"
Now, when you launch an Activity like MainActivity, Dagger will inject dependencies from your TestAppModule instead of AppModule before.
Moreover, If you want to #Inject to Test Class, you can add:
fun inject(testClass: TestClass) // On Your TestAppComponent
And then, you can call:
DaggerTestAppComponent.builder().application(app).build().inject(this) // This is on your TestClass
to Inject some dependencies to your TestClass.
Hope this can help you!!
I am presuming that you are trying to inject OkHttpClient:
#Inject
lateinit var okHttpClient: OkHttpClient
in your TestApp class, and it fails. In order to make it work, you will need to add an inject method in your TestAppComponent, to inject the overriden TestApp, so that it becomes:
#Singleton
#Component(
modules = [
AndroidInjectionModule::class,
AppModule::class,
TestRetrofitModule::class,
AppFeaturesModule::class,
RoomModule::class]
)
interface TestAppComponent : AppComponent {
#Component.Factory
interface Factory {
fun create(#BindsInstance application: App): TestAppComponent
}
fun inject(testApp: TestApp)
}
The reason why this is required, is because Dagger is type based, and uses the type of each class to be injected/provided to determine how to generate the code at compile-time. In your case, when you try to inject the TestApp, dagger will inject its superclass (the App class), because it only know that it has to inject the App class. If you have a look at the AndroidInjector interface (that you use in your AppComponent), you will see that it is declared like:
public interface AndroidInjector<T> {
void inject(T instance)
....
}
This means that it will generate a method:
fun inject(app App)
in the AppComponent. And this is why #Inject works in your App class, but does not work in your TestApp class, unless you explicitly provided it in the TestAppComponent.
Problem
I am building an app with a dynamic feature.
To provide all the dependencies to the main module and the feature module I am using dagger 2. The feature component is depending on the main component and because of that, the feature component is having a different scope than the main component scope (#Singleton in that case)
One of the interface injected in the main module are implemented on the feature module and provided by reflection in the main module. The implementation is also used in the feature module.
The problem that I have is that the instance provided in the main module is different from the one in the feature module (because of the scopes) but I would like to have just one instance provided with dagger.
Code
Here some code and you can find the whole example project in github
Dagger configuration for the main module:
TestModule.kt
#Module
class TestModule {
#Provides
#Singleton
fun provideTestA() : TestA = TestAImplementation()
private var testCProvider: TestC?= null
#Provides
#Singleton
fun provideTestC(testComponent: TestComponent) : TestC {
if(testCProvider != null) return testCProvider as TestC
val provider = Class.forName("com.example.feature.services.TestCImplementation\$Provider").kotlin.objectInstance as TestC.Provider
return provider
.get(testComponent)
.also { testCProvider = it }
}
}
TestComponent.kt
#Singleton
#Component(modules = [TestModule::class])
interface TestComponent {
fun inject(activity: MainActivity)
fun provideTestA() : TestA
}
Dagger configuration for the feature module:
TestDependencyModule.kt
#Module
class TestDependencyModule {
#Provides
#TestScope
fun provideTestB(): TestB = TestBImplementation()
#Provides
#TestScope
fun provideTestC(testB: TestB): TestC = TestCImplementation(testB)
}
TestDependencyComponent.kt
#TestScope
#Component(
modules = [TestDependencyModule::class],
dependencies = [TestComponent::class]
)
interface TestDependencyComponent {
fun inject(receiver: TestBroadcastReceiver)
fun testC(): TestC
}
Expected result
The interfaces TestC and TestA are injected in the MainActivity
The interfaces TestB and TestA are injected in the TestBroadcastReceiver
As expected the instance of the TestA implementation is unique but for the implementation of the TestB is not that way. As TestC depends on TestB the one injected in TestC is different from the one injected in the TestBroadcastReceiver with the #TestScope annotation.
So running the example project that you can find here I get the following log output
Instances injected in the MainActivity
D/TestB: instance 40525431
D/TestC: instance 119319268
D/TestA: instance 60713805
Instances injected in the TestBroadcastReceiver
D/TestB: instance 219966227
D/TestA: instance 60713805
I would like to share the same instance of TestB in both modules.
Any suggestion?
Thanks in advance!
TestDependencyComponent can not access TestC from the component dependency on TestComponent because TestComponent does not expose TestC on its public API. If you add fun testC(): TestC on TestComponent I expect you will then get a duplicate binding exception when processing TestDependencyComponent. From there you will need to determine the right way to provide the TestC instance.
I was building two instances of the DaggerTestDependencyComponent one in the Injector and another different one when you are implementing TestC
The solution that I found was the following:
Create an object where I can instantiate the TestDependencyComponent that will be shared with the Injector and the TestCImplementation
object FeatureInjector {
val testDependencyComponent: TestDependencyComponent by lazy {
DaggerTestDependencyComponent.builder()
.testComponent(com.example.daggertest.dagger.Injector.testComponent)
.build()
}
}
Now I modified my feature Injector like that:
object Injector {
lateinit var testDependencyComponent: TestDependencyComponent
#JvmStatic
internal fun getTestDependencyComponent(): TestDependencyComponent {
if (!::testDependencyComponent.isInitialized) {
testDependencyComponent = FeatureInjector.testDependencyComponent
}
return testDependencyComponent
}
}
And the TestCImplementation as follow:
class TestCImplementation #Inject constructor(
private val testB: TestB
) : TestC {
override fun testCFun() {
testB.testBFun()
Log.d("TestC", "instance ${System.identityHashCode(this)}")
}
companion object Provider : TestC.Provider {
override fun get(testComponent: TestComponent): TestC {
return FeatureInjector.testDependencyComponent.testC()
}
}
}
Running the code now I am getting the same instance of the TestB
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!