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)
...
Related
I want to use dagger for my mvp pattern,
but lateinit presenter will not initialized when I call its function.
Presenter is not private.
here is my dagger ViewModule which provide activity as view for presenter
#Module
class ViewModule {
#Provides
fun provideAView(): AView = MainActivity()
}
PresenterModule
#Module
class PresenterModule {
#Provides
fun provideAPresenter(repo: ARepo, view: AView): APresenter = APresenter(repo, view)
}
RepoModule
#Module
class RepoModule {
#Provides
fun provideARepo(): ARepo = ARepo()
}
And my APresenter constructor
class APresenter #Inject constructor(var repo: ARepo, var view: AView) {
fun showHelloWorld() {
val i = repo.repo()
Log.d("main", "aPresenter repo : $i")
view.helloWorld()
}
}
Component
#Component(modules = [PresenterModule::class, RepoModule::class, ViewModule::class])
#Singleton
interface PresenterComponent {
fun injectMain(view: AView)
}
MainActivity which implements AView interface and inject presenter
class MainActivity : AppCompatActivity(), AView, BView {
#Inject
lateinit var aPresenter: APresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val component = DaggerPresenterComponent.create()
component.injectMain(this)
// but this presenter will not init at this time and cause
// lateinit property not init exception.
aPresenter.showHelloWorld()
}
You need to specify the exact type in inject(...) methods of the Component interface. Otherwise, the members won't be injected. (See this comment for more explanation)
So change your component class to this:
#Component(modules = [PresenterModule::class, RepoModule::class, ViewModule::class])
#Singleton
interface PresenterComponent {
fun injectMain(view: MainActivity) //<-- The AView is changed to MainActivity
}
I am currently learning how to inject into Android apps with Dagger 2. I wrote a very basic code, but it refuses to work. My goals is it to inject the MainActicity as it should be. It builds
My code:
class MainActivity : AppCompatActivity() {
#Inject lateinit var info: Info
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
txt_view.text = info.textInformation
}
}
class Info {val textInformation = "You are able to read this" }
#Module
class InfoModule{
#Provides
fun info ():Info{
return Info()
}
}
class CustomApp : Application (),HasActivityInjector{
#Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidInjector
}
}
#Component(modules = arrayOf(AndroidInjectionModule::class,
ActivityModule::class))
interface ApplicationComponent{
fun inject(application: CustomApp)
}
#Module
abstract class ActivityModule{
#ContributesAndroidInjector(modules = arrayOf(InfoModule::class))
abstract fun contributeInfoActivityInjector():MainActivity
}
The answer is that I forgot to add this code to the CustomApp class, and .CustomApp to the manifest file
override fun onCreate() {
super.onCreate()
initDi()
}
private fun initDi() {
DaggerApplicationComponent.builder().build().inject(this)
}
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()
}
}
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/
I am trying to get dagger working in my application.
After creating Module Component and MyApp i can use dagger to inject database service into view but i am having trouble doing same thing with presenter.
Code:
class MyApp : Application() {
var daoComponent: DaoComponent? = null
private set
override fun onCreate() {
super.onCreate()
daoComponent = DaggerDaoComponent.builder()
.appModule(AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.daoModule(DaoModule())
.build()
}
}
Module
#Module
class DaoModule {
#Provides
fun providesEstateService(): EstateService = EstateServiceImpl()
}
Component
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(activity: MainActivity)
}
AppModule
#Module
class AppModule(internal var mApplication: Application) {
#Provides
#Singleton
internal fun providesApplication(): Application {
return mApplication
}
}
MainActivity
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
#Inject
lateinit var estateService : EstateService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as MyApp).daoComponent!!.inject(this)estateService.numberOfInvoicedEstates.toString()
}
override fun createPresenter(): MainPresenter = MainPresenterImpl()
}
After injecting estateService this way I can use it, but I cant figure out how do I inject service directly into the presenter.
I tried doing it like this but it isn't working.
Should I just pass injected objects from the activity? or maybe I should pass MyApp as an argument or make static method allowing my to get it from any place in the application?
class MainPresenterImpl
#Inject
constructor(): MvpBasePresenter<MainView>(),MainPresenter {
#Inject
lateinit var estateService : EstateService
}
Your component should provide the presenter like that:
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
#Singleton
interface MyComponent {
mainPresenter() : MainPresenter
}
And all dependencies the presenter needs are injected to the presenter via constructor parameters:
class MainPresenterImpl
#Inject constructor(private val estateService : EstateService ) :
MvpBasePresenter<MainView>(),MainPresenter {
...
}
Than in createPresenter() just grab the presenter from dagger component like this:
class MainActivity : MvpActivity<MainView, MainPresenter>(), MainView {
...
override fun createPresenter(): MainPresenter =
(application as MyApp).myComponent().mainPresenter()
}
Please note that the code shown above will not compile. This is just pseudocode to give you an idea how the dependency graph could look like in Dagger.
Please refer to this example on how to use Dagger 2 in combination with MVP.
You have to tell your component where you want to inject it.
So, try with this component:
#Singleton
#Component(modules = arrayOf(AppModule::class, DaoModule::class))
interface DaoComponent {
fun inject(presenter: MainPresenterImpl)
}