I have a hard time figuring out how to set up Dagger component hierarchy to match my vision. Any help would be greatly appreciated and please feel free to criticise the vision if you find any holes in it.
Let's get to the point.
I'm trying to redesing part of my application related to user registration. Since registration is a self contained domain I assume that it's a good candidate for a separate dagger component(RegistrationSubcomponent). Within that registration domain I will show an activity(RegistrationActivity) and on top of it few fragments(RegistrationFragment1, RegistrationFragment2, ...). I already have an application component(AppComponent) with the life scope of an application which should be a parent for RegistrationSubcomponent. AppComponent is used to inject dependecies into other activites excluding RegistrationActivity but also to a common activity(CommonActivity) which is a parent for every activity in my app including RegistrationActivity. To inject dependencies into RegistrationActivity i want to use RegistrationSubcomponent.
The RegistrationSubcomponent should be instantiated shortly before displaying RegistrationActivity and should live until after the RegistrationActivity returns. I dont't want to bind RegistrationSubcomponent lifecycle with RegistrationActivity lifecycle. Mayby in this case it may have sense but in future I will probably would like to create components for other app domains which may not neceserralily match the lifecycle of activities they encompass.
In general the problem is injection of dependecies specfied in RegistrationComponent into RegistrationActivity as well as to RegistrationFragments.
The problem is that the app crashes when navigating to RegistrationActivity with following error:
Unable to start activity ComponentInfo{pl.kermit.diproblem/pl.kermit.diproblem.activities.RegistrationActivity}: java.lang.IllegalArgumentException: No injector factory bound for Class
Minimal project presenting the problem can be found at https://github.com/mariola3000/DIProblemStack01
Below I put the most important parts of code
// APP COMPONENT
#Singleton
#Component(modules = [AndroidInjectionModule::class, AppModule::class, AppAndroidBindingModule::class,
AndroidSupportInjectionModule::class])
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(application: Application)
}
#Module(subcomponents = [RegistrationComponent::class])
class AppModule {
#Provides
fun provideGlobalDependency(): GlobalDependency {
return GlobalDependency("GlobalDependency")
}
#Provides
fun provideGlobal2Dependency(): Global2Dependency {
return Global2Dependency("Global2Dependency")
}
}
#Module
abstract class AppAndroidBindingModule {
#PerActivity
#ContributesAndroidInjector()
abstract fun bindMainActivity(): MainActivity
}
// REGISTRATION COMPONENT
#PerRegistration
#Subcomponent(modules = [RegistrationModule::class, RegistrationAndroidBindingModule::class])
interface RegistrationComponent {
#Subcomponent.Builder
interface Builder {
fun requestModule(module: RegistrationModule): Builder
fun build(): RegistrationComponent
}
}
#Module
class RegistrationModule {
#PerRegistration
#Provides
fun provideRegistrationDependency(): RegistrationDependency {
return RegistrationDependency("RegistrationDependency")
}
#PerRegistration
#Provides
fun provideRegistrationFragmentDependency(): RegistrationFragmentDependency {
return RegistrationFragmentDependency("RegistrationFragmentDependency")
}
}
#Module
abstract class RegistrationAndroidBindingModule {
#PerActivity
#ContributesAndroidInjector
abstract fun bindRegistrationActivity(): RegistrationActivity
#PerFragment
#ContributesAndroidInjector
abstract fun bindRegistrationFragment(): RegistrationFragment
}
// ACTIVITIES
abstract class CommonActivity : DaggerAppCompatActivity(){
#Inject
lateinit var globalDependency: GlobalDependency
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
Toast.makeText(this, "global message: " + globalDependency.message, Toast.LENGTH_LONG).show()
}
}
class MainActivity : CommonActivity() {
#set:Inject
lateinit var global2Dependency: Global2Dependency
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val text: TextView = findViewById(R.id.main_text)
text.text = global2Dependency.message
val button: Button = findViewById(R.id.main_button)
button.setOnClickListener {
startActivity(Intent(this, RegistrationActivity::class.java))
}
}
}
class RegistrationActivity : CommonActivity() {
#Inject
lateinit var registrationDependency: RegistrationDependency
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_registration)
val registrationMessage = findViewById<TextView>(R.id.registration_text)
registrationMessage.text = registrationDependency.message
val transaction = this.supportFragmentManager.beginTransaction()
val fragment = RegistrationFragment()
transaction.replace(R.id.registration_fragment_container, fragment)
transaction.addToBackStack(fragment.javaClass.simpleName)
transaction.commit()
}
}
Related
Trying to understand the following example and do something similar.
In my application there is a module, in which I want to use Dagger.
To do this I need an Application class in which I initialize and store AppComponent
Judging from the documentation I need to create an interface with a component from my module:
interface PasscodeSetupComponentProvider {
fun providePasscodeSetupComponent(): PasscodeComponent
}
Then I will implement this interface for my Application class:
open class FenturyApplication : PasscodeSetupComponentProvider {
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.builder()
.appModule(AppModule(applicationContext))
.build()
}
override fun providePasscodeSetupComponent(): PasscodeComponent {
return appComponent.passcodeComponent
}
}
But judging by the example from the documentation I don't understand what should be in my interface Appcomponent namely passcodeComponent.
In the example, it looks like this:
class MyApplication: Application(), LoginComponentProvider {
// Reference to the application graph that is used across the whole app
val appComponent = DaggerApplicationComponent.create()
override fun provideLoginComponent(): LoginComponent {
return appComponent.loginComponent().create()
}
}
I added the following code to my AppComponent:
#Component(modules = [AppModule::class])
#Singleton
interface AppComponent {
val applicationContext: Context
fun passcodeComponent(): PasscodeComponent
}
And if I understood correctly, then in the fragment that is in my module, I can write the following:
lateinit var passcodeComponent: PasscodeComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val context = activity?.applicationContext ?: return
passcodeComponent = (context as PasscodeSetupComponentProvider).providePasscodeSetupComponent()
passcodeComponent.inject(this)
After that I hope I can use dagger in my module.
You're missing a crucial part of how AppComponent is written. You can read the complete code in this link.
Please note how Google is declaring the LoginComponent (as Subcomponent) and how do they declare it inside AppComponent in order do return a new instance of LoginComponent.
AppComponent (copied from the link I posted)
#Singleton
#Component(modules = [NetworkModule::class, SubcomponentsModule::class])
interface ApplicationComponent {
// This function exposes the LoginComponent Factory out of the graph so consumers
// can use it to obtain new instances of LoginComponent
fun loginComponent(): LoginComponent.Factory
}
And the code for LoginComponent
#Subcomponent
interface LoginComponent {
// Factory that is used to create instances of this subcomponent
#Subcomponent.Factory
interface Factory {
fun create(): LoginComponent
}
fun inject(loginActivity: LoginActivity)
}
I'm trying to add UI tests for the first time to an app, and I was looking at using espresso.
The app uses Dagger2 for DI with #Inject annotations for classes that should be injectable, and AndroidInjection/AndroidSupportInjection in screens (Activity / Fragment).
class Application : android.app.Application(), HasActivityInjector, HasServiceInjector {
...
override fun onCreate() {
super.onCreate()
initDagger()
Timber.d("Application initialized successfully!")
}
protected open fun initDagger() {
Components.initialize(this)
}
}
object Components : ComponentFactory {
private lateinit var sComponent: AppComponent
fun initialize(app: Application) {
sAppComponent = DaggerAppComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
// overrides
}
interface ComponentFactory {
fun app(): AppComponent
fun authenticated(): AuthenticatedComponent
}
Next the Components and Modules. The AppActivitiesModule and AuthenticatedActivitiesModule are classes with #ContributesAndroidInjector for screens.
#Singleton
#Component(modules = [AppModule::class, AppActivitiesModule::class, AndroidInjectionModule::class, AndroidSupportInjectionModule::class])
interface AppComponent {
fun authenticatedComponentBuilder(): AuthenticatedComponent.Builder
fun inject(app: Application)
#Component.Builder
interface Builder {
fun build(): AppComponent
fun applicationModule(applicationModule: ApplicationModule): Builder
}
}
#Module
open class AppModule(private val application: Application) {
// some #Provides
}
#AuthenticatedScope
#Subcomponent(modules = [AuthenticatedModule::class, AuthenticatedActivitiesModule::class])
interface AuthenticatedComponent {
fun inject(application: Application)
#Subcomponent.Builder
interface Builder {
fun userModule(module: UserModule): Builder
fun build(): AuthenticatedComponent
}
}
#Module
class AuthenticatedModule(private val userId: Long,
private val userRole: User.Role) {
// Some #Provides #AuthenticatedScope
}
And a typical use case would be:
#Singleton
class AppLevelService
#Inject constructor(...) { ... }
#AuthenticatedScope
class AuthenticatedLevelServices
#Inject constructor(...) { ... }
class ViewModel
#Inject constructor(private val appService: AppLevelService,
private val authService: AuthenticatedLevelServices) { ... }
class MyActivity : BaseActivity {
#Inject
lateinit var vmProvider: Provide<ViewModel>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
}
}
How can I make the test setup for this "type" of dagger usage?
I find a lot of examples of testing with dagger2 but for #Provides annotations, and I'm thinking there should be a way to mock injectable classes with #Inject.
I've tried DaggerMock but I get:
You must define overridden objects using a #Provides annotated method instead of using #Inject annotation
Not that it should affect anything, but I'm also using a custom Runner with DexOpener.
Any ideas or good documentation / examples for testing this setup?
I am new to unit testing in Android and have gone through several tutorials to get myself familiar with mockito and robolectric.
My app is using Dagger 2 to inject my EventService into my MainActivity. For my MainActivityUnitTest, I have set up a TestServicesModule to provide a mocked version of EventService so that I can use Robolectric to run unit tests against my MainActivity
I'm having an issue getting the ServiceCallback on my EventService.getAllEvents(callback: ServiceCallback) to execute in the unit test. I have verified in the #Setup of my MainActivityUnitTest class that the EventService is being injected as a mocked object. I have gone through several tutorials and blog posts and as far as I can tell, I am doing everything correctly. The refreshData() function in MainActivity is getting called successfully, and I can see that the call to eventsService.getAllEvents(callback) is being executed. But the doAnswer {} lambda function is never getting executed.
Here's my relevant code:
AppComponent.kt
#Singleton
#Component(modules = [
AppModule::class,
ServicesModule::class,
FirebaseModule::class
])
interface AppComponent {
fun inject(target: MainActivity)
}
ServicesModule.kt
#Module
open class ServicesModule {
#Provides
#Singleton
open fun provideEventService(db: FirebaseFirestore): EventsService {
return EventsServiceImpl(db)
}
}
EventsService.kt
interface EventsService {
fun getAllEvents(callback: ServiceCallback<List<Event>>)
fun getEvent(id: String, callback: ServiceCallback<Event?>)
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
#Inject lateinit var eventsService: EventsService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as App).appComponent.inject(this)
...
}
override fun onStart() {
super.onStart()
refreshData()
}
eventsService.getAllEvents(object: ServiceCallback<List<Event>> {
override fun onCompletion(result: List<Event>) {
viewModel.allEvents.value = result
loading_progress.hide()
}
})
}
Now we get into the tests:
TestAppComponent.kt
#Singleton
#Component(modules = [
TestServicesModule::class
])
interface TestAppComponent : AppComponent {
fun inject(target: MainActivityUnitTest)
}
TestServicesModule.kt
#Module
class TestServicesModule {
#Provides
#Singleton
fun provideEventsService(): EventsService {
return mock()
}
}
MainActivityUnitTest.kt
#RunWith(RobolectricTestRunner::class)
#Config(application = TestApp::class)
class MainActivityUnitTest {
#Inject lateinit var eventsService: EventsService
#Before
fun setup() {
val testComponent = DaggerTestAppComponent.builder().build()
testComponent.inject(this)
}
#Test
fun givenActivityStarted_whenLoadFailed_shouldDisplayNoEventsMessage() {
val events = ArrayList<Event>()
doAnswer {
//this block is never hit during debug
val callback: ServiceCallback<List<Event>> = it.getArgument(0)
callback.onCompletion(events)
}.whenever(eventsService).getAllEvents(any())
val activity = Robolectric.buildActivity(MainActivity::class.java).create().start().visible().get()
val noEventsView = activity.findViewById(R.id.no_events) as View
//this always evaluates to null because the callback is never set from the doAnswer lambda
assertThat(callback).isNotNull()
verify(callback)!!.onCompletion(events)
assertThat(noEventsView.visibility).isEqualTo(View.VISIBLE)
}
}
Edit: Adding App and TestApp
open class App : Application() {
private val TAG = this::class.qualifiedName
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
}
open fun initDagger(app: App): AppComponent {
return DaggerAppComponent.builder().appModule(AppModule(app)).build()
}
}
class TestApp : App() {
override fun initDagger(app: App): AppComponent {
return DaggerTestAppComponent.builder().build()
}
}
It looks like you're using a different component to inject your test and activity. As they're different components I suspect you are using 2 different instances of the eventsService.
Your test uses a local DaggerTestAppComponent.
#Inject lateinit var eventsService: EventsService
#Before
fun setup() {
val testComponent = DaggerTestAppComponent.builder().build()
testComponent.inject(this)
}
While your Activity uses the appComponent from the application.
class MainActivity : AppCompatActivity() {
#Inject lateinit var eventsService: EventsService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as App).appComponent.inject(this)
...
}
To overcome this you may consider adding a test version of your application class, this would allow you to replace the AppComponent in your application with your TestAppComponent. Robolectric should allow you to create a test application as follows: http://robolectric.org/custom-test-runner/
please help me! I have a trouble in use of dagger 2.
I want to bind some dependency in runtime not in compile time inside MainActivity by using #Subcomponent.Builder and #BindsInstance
I have an ApplicationComponent and it has a Builder and its #BindsInstance looks working fine. I can use like below
DaggerApplicationComponent
.builder()
.application(this)
.build()
.inject(this)
but some trouble came from MainActivity...
below are snippets of codes
[ApplicationComponent]
#Singleton
#Component(modules = [ApplicationModule::class])
internal interface ApplicationComponent : AndroidInjector<MyApplication> {
#Component.Builder
interface Builder {
#BindsInstance
fun application(application: Application): Builder
fun build(): ApplicationComponent
}
}
[ApplicationModule]
#Module(
includes = [
AndroidInjectionModule::class,
AndroidSupportInjectionModule::class,
ActivityInjectionModule::class
],
subcomponents = [
MainComponent::class
]
)
internal abstract class ApplicationModule {
#PerActivity
#ContributesAndroidInjector(modules = [SplashModule::class])
abstract fun splashActivity(): SplashActivity
#Binds
#IntoMap
#ActivityKey(MainActivity::class)
abstract fun mainActivity(builder: MainComponent.Builder): AndroidInjector.Factory<out Activity>
}
[MainComponent]
#PerActivity
#Subcomponent(modules = [MainModule::class])
internal interface MainComponent : AndroidInjector<MainActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>() {
#BindsInstance
abstract fun testClass(mainTestClass: MainTestClass): Builder
}
}
[MainActivity]
internal class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// This works find without runtime injection
// AndroidInjection.inject(this)
/**
*I want to bind some dependency(in this case, MainTestClass) in runtime like below.
* so that I can use MainTestClass inside MainModule to inject this to other classes.
* but, for some reason,
* DaggerMainComponent IS NOT GENERATED AUTOMATICALLY...
*/
DaggerMainComponent.builder()
.testClass(MainTestClass())
.build()
.inject(this);
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startActivity(Intent(this, SplashActivity::class.java))
}
}
The problem is that I cannot access DaggerMainComponent because the Dagger doesn't generate it automatically.
I am searching for lots of websites to solve this but failed.
Is there any way to make it?
I have found a way to achieve what you want, I think. Apologies for not using your particular example, but it was easier to paste in the code I have and know that works from my own IDE. I've added comments on the critical lines. Here's the code:
Singleton component
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
RuntimeBindingModule::class // my addition!
])
interface MainApplicationComponent {
fun inject(app: MainApplication)
// my addition!
fun runtimeBuilder(): RuntimeBindingActivitySubcomponent.Builder
#Component.Builder
interface Builder {
fun build(): MainApplicationComponent
#BindsInstance fun app(app: Context): Builder
}
}
This binding code is essentially identical to yours.
#Subcomponent
interface RuntimeBindingSubcomponent : AndroidInjector<RuntimeBindingActivity> {
#Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<RuntimeBindingActivity>() {
#BindsInstance abstract fun bindInt(intVal: Int): Builder
}
}
#Module(subcomponents = [RuntimeBindingSubcomponent::class])
abstract class RuntimeBindingActivityModule {
#Binds #IntoMap #ActivityKey(RuntimeBindingActivity::class)
abstract fun bindInjectorFactory(
builder: RuntimeBindingActivitySubcomponent.Builder
): AndroidInjector.Factory<out Activity>
}
MainApplication
open class MainApplication : Application(), HasActivityInjector {
// This needs to be accessible to your Activities
lateinit var component: MainApplication.MainApplicationComponent
override fun onCreate() {
super.onCreate()
initDagger()
}
private fun initDagger() {
component = DaggerMainApplicationComponent.builder()
.app(this)
.build()
component.inject(this)
}
}
RuntimeBindingActivity
class RuntimeBindingActivity : AppCompatActivity() {
// I had to use #set:Inject because this is a primitive and we can't use lateinit
// on primitives. But for your case,
// `#Inject lateinit var mainTestClass: MainTestClass` would be fine
#set:Inject var intVal: Int = -1
override fun onCreate(savedInstanceState: Bundle?) {
// And this is how you can get runtime binding
val subComponent = (application as MainApplication).component.runtimeBuilder()
with(subComponent) {
seedInstance(this#RuntimeBindingActivity)
bindInt(10) // runtime binding
build()
}.inject(this)
Log.d("RuntimeBindingActivity", "intVal = $intVal")
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_runtime_binding)
}
}
It is critically important to note that the subcomponent which you generate in this way doesn't get magically stored somewhere by dagger. If you want your late-bound instance to be available for injecting into other classes controlled by your #PerActivity scope, you need to manually manage the lifecycle of this subcomponent. Store it somewhere (maybe in your custom Application class), and then you also must set its reference to null when your activity is destroyed, or you'll be leaking that activity.
I am a newbie to android and trying to use Dagger2. I spend whole night and still dont know why my dagger does not provide presenter. Here are my code (I use Kotlin)
AppComponent
#Singleton
#Component(modules = arrayOf(PresenterModule::class))
interface AppComponent {
fun inject(target: SplashActivity)
}
PresenterModule
#Module
class PresenterModule {
#Provides
#Singleton
fun provideSplashPresenter(): SplashPresenter {
return SplashPresenter()
}
}
App
class App: Application() {
companion object {
lateinit var appComponent: AppComponent
}
override fun onCreate() {
super.onCreate()
appComponent = initDagger()
}
private fun initDagger(): AppComponent {
return DaggerAppComponent.create()
}
}
This is the presenter
class SplashPresenter: BasePresenterImpl<SplashContract.View>(), SplashContract.Presenter {
override fun performToast(mess: String) {
logi("abc", "performToast")
logi("abc", "mess: " + mess)
mView?.showLoading()
if (mess.isNullOrBlank()) {
mView?.showTosat("this is empty mess") ?: logi("abc", "null")
} else {
mView?.showTosat(mess) ?: logi("abc", "null")
}
mView?.hideLoading()
}
}
And finally, this is my SplashActivity
class SplashActivity : BaseActivity(), SplashContract.View {
#Inject
lateinit var presenter: SplashPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
presenter.attachView(this)
//TODO: check log in
//TODO: If logged in => start main screen
//TODO: If not logged in => load login activity
button.setOnClickListener{
presenter.performToast(editText.text.toString())
logi("abc", "perform clicked")
}
}
}
When I run these code, I got this error
Lateinit property presenter has not been initialized, which means that "Inject" does not work
Since you're not using constructor injection here (which you can't, because you don't 'own' the activity's constructor) Dagger does not 'know' that it has to inject something into your Activity.
You have to manually inject like this:
(applicationContext as App).appComponent.inject(this)
in your SplashActivity's onCreate() method (before using the presenter, of course).
Second, your presenter needs a constructor that tells Dagger how to construct/'build' the presenter, which means a constructor annotated with the #Inject annotation, so:
class SplashPresenter #Inject constructor(): BasePresenterImpl<SplashContract.View>(), SplashContract.Presenter
You forgot to inject the SplashActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
App.appComponent.inject(this)
setContentView(R.layout.activity_splash)
...