Missing Binding on dagger 2 injection - android

I'm getting this error with Dagger 2
C:\Users\Anon\AndroidStudioProjects\BarreChat1082\app\build\tmp\kapt3\stubs\debu
g\com\example\barrechat108\Dependencies\RepositoryComponent.java:8: error:
[Dagger/MissingBinding] com.example.barrechat108.MainActivity cannot be
provided without an #Inject constructor or an #Provides-annotated method.
public abstract interface RepositoryComponent {
^
com.example.barrechat108.MainActivity is injected at
com.example.barrechat108.Dependencies.RepositoryComponent.inject(com.example.barrechat108.MainActivity)
I thought I had set up the injection correctly, My modules are
#Module
class RepositoryModule {
//Provide singleton instance of our database for use by entire app.
//Set up in application's onCreate method
#Provides
#Singleton
fun providesDatabase(applicationContext: Application) : AppDatabase {
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "AppDatabase"
).build()
return db
}
#Provides
#Singleton
fun providesRepository(database: AppDatabase) : Repository {
val repos = Repository(database)
return repos
}
}
and
#Module
class AppModule (application: Application) {
val mApplication = application
#Provides
#Singleton
fun providesApplication() : Application {
return mApplication
}
}
The component used for injection to the MainActivity is
#Singleton
#Component(modules=[AppModule::class, RepositoryModule::class])
interface RepositoryComponent{
fun inject(activity: MainActivity)
}
Which is where the error is contained.
To build the instance of Dagger2, I extended from the Application class and in the manifest I told the app to use this code,
class BarreApp : Application() {
private lateinit var mRepositoryComponent: RepositoryComponent
override fun onCreate() {
super.onCreate()
mRepositoryComponent = DaggerRepositoryComponent.builder()
.appModule(AppModule(this))
.repositoryModule(RepositoryModule())
.build()
}
fun getRepositoryComponent(): RepositoryComponent {
return mRepositoryComponent
}
}
Finally, the activity I use the data injection in is the MainActivity. In this Main Activity, I create an instance of the Repository and send it to the view model from this start activity. I get rid of re-assignment by checking whether or not the repository was already set in the viewmodel. So each time the MainActiviy goes through on create, it opens the viewmodel and checks whether it has a repository and if it doesn't it pulls the repository that was injected on it and puts it into the ViewModel.
class MainActivity : AppCompatActivity() {
private lateinit var mPager: ViewPager
#Inject
private lateinit var mRepository: Repository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.barreBar))
//Get Repository Component using Dagger2
val mApp = application as BarreApp
mApp.getRepositoryComponent().inject(this)
//Get the main ViewModel
val barreViewModel = ViewModelProviders.of(this).get(BarreViewModel :: class.java)
//Add repository to ViewModel
barreViewModel.setRepository(mRepository)
}

Related

Android Kotlin: integration tests using Dagger 2 and Mockito getting error, "zero interactions with this mock"

I am developing an Android application using Kotlin. I am writing integration tests for my application. Now I am having a problem mocking an injected dependency class that is injected using the Dagger 2 test if a method of the mocked object is called. Following is my code.
I have the TestAppComponent class with the following code
#Singleton
#Component(modules = [ TestAppModule::class ])
interface TestAppComponent
{
fun inject(app: ApplicationController)
fun inject(app: MockApplicationController)
}
As you can see in the class, I have two inject methods. One for the ApplicationController which basically is the application class used for the actual application. Another one for MockAppllication controller which is also the application class but it is used for the tests.
Following is the implementation of the ApplicationController class
open class ApplicationController: Application()
{
lateinit var appComponent: AppComponent
private fun initDagger(app: ApplicationController): AppComponent = DaggerAppComponent
.builder()
.appModule(AppModule(app)).build()
#Inject
lateinit var fileService: IFileService
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
As you can see in the code, I am initializing the Dagger AppComponent class in the onCreate method of the application class. Then inject the dependency class.
This is the implementation of my MockApplicationController class which is used for the tests.
class MockApplicationController: ApplicationController()
{
private fun initDagger(app: MockApplicationController): AppComponent = DaggerTestAppComponent
.builder()
.testAppModule(TestAppModule(app))
.build()
override fun onCreate() {
super.onCreate()
//override the dagger app component appComponent
this.appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
This is my activity class.
class MainActivity: AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?) {
//rest of the code
button_save_file.setOnClickListener {
//rest of the code
ApplicationController.instance.fileService.saveFile(filePath)
}
}
}
As you can see, basically, what I am doing within the activity class is that I am just calling the saveFile of the IFileService interface.
Following is the definition of the IFileService interface
interface IFileService
{
fun saveFile(path: String)
}
There are two classes that are implementing the IFileService. One is FakeFileService class which will be used for the tests and the other one is FileService class which will be used for the actual application.
Following is the implementation of FakeFileService class
class FakeFileService: IFileService
{
fun saveFile(path: String)
{
//literally, this is doing nothing since we will only test that if the method is called
}
}
I also created two classes for the Dagger app modules. One for the tests and the other one for the actual application.
Following is the implementation of the TestAppModule class which will be used for tests.
#Module
open class TestAppModule (private val app: Application) {
private var fileService: IFileService? = null
#Singleton
#Provides
fun provideContext(): Context = app
//to override injecting the Mockito mock instance
fun injectFileService(fileService: IFileService) {
this.fileService = fileService
}
#Singleton
#Provides
open fun fileService(): IFileService {
if (this.fileService != null) {
return this.fileService as IFileService
}
return FakeFileService()
}
}
Note the injectFileService method. I created that method to inject the mock object/ instance mocked using Mockito within the tests.
This is my test class
#RunWith(AndroidJUnit4::class)
#LargeTest
class CameraTest: TestBuilder()
{
#get:Rule
var mainActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule<MainActivity>(CameraActivity::class.java, true, false)
private lateinit var fileServiceMock: IFileIOService
#Before
fun setUp() {
this.fileServiceMock = mock(IFileService::class.java)
MockitoAnnotations.initMocks(this)
val instrumentation = InstrumentationRegistry.getInstrumentation()
val app = instrumentation.targetContext.applicationContext as MockApplicationController
var testModule = TestAppModule(app)
testModule.injectFileService(this.fileServiceMock)
app.appComponent = DaggerTestAppComponent
.builder()
.testAppModule(testModule)
.build()
app.appComponent.inject(app)
}
#Test
fun itSavesFileWhenButtonIsClicked() {
Intents.init()
var targetContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
var intent: Intent = Intent(targetContext, MainActivity::class.java)
this.mainActivityRule.launchActivity(intent)
onView(withId(R.id.button_save_file)).perform(click())
verify(this.fileServiceMock).saveFile(any())
Intents.release()
}
}
As you can see, in my test class, I try to inject the mocked version of IFileService object before the test is run. Then in my test, I am asserting that the method is executed. When I run the test it is complaining that "zero interactions with this mock". How can I fix it? What is wrong with my code and how can I fix it?
This is my ApplicationController class
open class ApplicationController: Application()
{
lateinit var appComponent: AppComponent
private fun initDagger(app: ApplicationController): AppComponent = DaggerAppComponent
.builder()
.appModule(AppModule(app)).build()
#Inject
lateinit var fileService: IFileService
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
When I put the loggin in each onCreate method of application controller classes and #Before method, they are called in the following order.
ApplicationController
MockApplicationController
The code to modify the MockApplicationController with the #Before method

Android Dagger 2.10 to 2.14.1 - Inject Custom Class

I am using Dagger 2.14.1 in my Android application and migrated recently from 2.10. The new version makes it much easier to inject Activities and Fragments, but I can't find a way to inject a custom class where I cannot change the constructor as well. With 2.10 I could write custom inject functions and then use them to simply inject any class:
Simplified Dagger 2.10 Injection with Kotlin:
#Singleton
#Component(dependencies = [], modules = [ApplicationModule::class, ...])
interface ApplicationComponent {
fun application(): App
fun inject(authenticationActivity: AuthenticationActivity)
fun inject(converters: Converters)
// ...
}
class App : Application() {
companion object {
#JvmStatic
lateinit var component: ApplicationComponent
}
override fun onCreate() {
super.onCreate()
component = DaggerApplicationComponent.builder()
.applicationModule(ApplicationModule(this))
.build()
}
}
// This class is used by the Room database framwork and I cannot change the constructor and do class Converters #Inject constructor(private val gson: Gson) {
class Converters {
#Inject
protected lateinit var gson: Gson
init {
App.component.inject(this)
}
// ...
}
Simplified Dagger 2.14.1 Injection with Kotlin doesn't provide me with a ApplicationComponent Object to inject my custom classess:
#Singleton
#Component(modules = [ApplicationModule::class, ...])
interface ApplicationComponent : AndroidInjector<App> {
#Component.Builder
abstract class Builder : AndroidInjector.Builder<App>()
fun inject(converters: Converters) // doesn't work!
}
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
// From where do I get my component to call .inject() from another class?
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
In short: the DaggerApplicationComponent.builder().create(this) returns an AndroidInjector<App!> Object. This Object only has one .inject() functions which only accepts my App Class. So I cannot inject anything else.
Of course there is a lot missing, but I wanted to only post the relevant code. My Dagger 2.14.1 implementation works and all my dependencies in Activities, Fragments and View Models get injected.
I am quite new to Dagger and the more I use it the more I love it, but I couldn't figure out on how to inject custom classes where I cannot change the constructor. Thanks for you help!
Try this:
class App : Application(), HasActivityInjector {
#Inject
lateinit var activityInjector: DispatchingAndroidInjector<Activity>
companion object {
#JvmStatic
lateinit var instance: App
}
#Inject
lateinit var component: ApplicationComponent
override fun onCreate() {
super.onCreate()
instance = this
DaggerApplicationComponent.builder().create(this).inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> = activityInjector
}
Then
object Injector {
#JvmStatic fun get(): ApplicationComponent = App.instance.component
}
Now you can do
class Converters {
#Inject
protected lateinit var gson: Gson
init {
Injector.get().inject(this)
}

Dagger 2 does not generate file correctly in Android Studio

In my Android app I have a trouble with Dagger 2. I have a module that contains 3 dependencies, two of them are injected in activity but not the third one.
#Module
class MyNumberWriterModule(_type: Boolean) {
private val type = _type
#Provides
#Singleton
fun provideSimpleNumberWriter(): SimpleNumberWriter {
return if (type) BelgianSimpleNumberWriter() else FrenchSimpleNumberWriter()
}
#Provides
#Singleton
fun provideIntegerNumberWriter(simpleNumberWriter: SimpleNumberWriter): IntWriter {
return IntegerNumberWriter(simpleNumberWriter)
}
#Provides
#Singleton
fun provideDecimalNumberWriter(intWriter: IntWriter): NumberWriter {
return DoubleNumberWriter(intWriter)
}
}
And here is a component
#Singleton
#Component(modules = [(MyNumberWriterModule::class)])
internal interface MyNumberWriterComponent {
fun inject(mainActivity: MainActivity)
// fun inject(baseActivity: _BaseActivity)
}
My activity
class MainActivity : _BaseActivity() {
#Inject
lateinit var numberWriter: IntWriter
#Inject
lateinit var numberWriter2: NumberWriter
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
DaggerMyNumberWriterComponent.builder()
.myNumberWriterModule(MyNumberWriterModule(true))
.build()
.inject(this)
// Here numberWriter is injected
// Here numberWriter2 is NOT injected
}
}
Any ideas why the third #Provides cannot be injected? I don't see any error messages but in the module Android Studio shows that provideDecimalNumberWriter is never used. There are no errors when I run the project
After a hours of experiments I created my class that I'm injecting DoubleNumberWriter and interface NumberWriter that implements my business logic in the package with another name and it started to work. Maybe dagger didn't like previous package name double

Android: Internal Compiler error while using Dagger 2 (Kotlin)

I am trying to use Dagger 2 in Android. Implementation is very straightforward. Below are dagger 2 related Components and Modules:
ApplicationComponent.kt
#Singleton
#Component(modules = arrayOf(AppModule::class))
interface ApplicationComponent {
fun inject(app: Application)
fun inject(mainActivity: MainActivity)
fun plusMainAcitvityComponent(mainActivityModule: MainActivityModule): MainActivityComponent
}
MainActivityComponent.kt
#Subcomponent(modules = arrayOf(MainActivityModule::class, GoogleApiClientModule::class))
interface MainActivityComponent{
fun inject(mainActivity: MainActivity)
}
AppModule.kt
#Module
class AppModule(val app: Application){
#ApplicationContext
#Provides
fun providesApplicationContext() = app
}
GoogleApiClientModule.kt
#Module
class GoogleApiClientModule{
companion object {
val TAG = "GoogleApiClientModule"
}
#Singleton
#Provides
fun providesGoogleApiClient(#ActivityContext activity: MainActivity, connectionFailedListener: OnConnectionFailedListener) =
GoogleApiClient.Builder(activity)
.addApi(Places.GEO_DATA_API)
.addApi(Places.PLACE_DETECTION_API)
.enableAutoManage(activity, connectionFailedListener)
.build()
#Singleton
#Provides
fun providesConnectionFailedListener() = OnConnectionFailedListener {
Log.d(TAG, "GAC connection has failed")
}
}
GooglePlacesClientModule.kt
#Module
class GooglePlacesClientModule {
#Provides
fun providesGeoDataClient(#ActivityContext activity: MainActivity) = Places.getGeoDataClient(activity, null)
}
MainActivityModule.kt
#Module
class MainActivityModule(val activity: MainActivity){
#ActivityContext
#Provides
fun providesActivityContext() = activity
}
Below is my Main Activity where I am injecting the above components:
class MainActivity : AppCompatActivity(), OnMapReadyCallback {
lateinit var binding: ActivityMainBinding
lateinit var mapFragment: SupportMapFragment
#Inject lateinit var app: App
#Inject lateinit var mainViewModelFactory: MainViewModelFactory
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Data binding
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
//Dagger inject
App.appComponent.inject(this)
app.plustMainAcitvityComponent(this)?.inject(this)
//Viewmodel init
mainViewModel = ViewModelProviders.of(this, mainViewModelFactory).get(MainViewModel::class.java)
//Ui init
setSupportActionBar(binding.toolbarLayout?.toolbar)
setupBottomSheetLayout()
setupGoogleMap()
}
----
---
}
Below is my App where Dagger is initialized:
class App: Application(){
var mainActivityComponent: MainActivityComponent? = null
companion object {
lateinit var appComponent: ApplicationComponent
}
override fun onCreate() {
super.onCreate()
setupDagger()
}
fun setupDagger(){
appComponent = DaggerApplicationComponent.builder().appModule(AppModule(this)).build()
}
fun plustMainAcitvityComponent(activity: MainActivity) : MainActivityComponent?{
if(mainActivityComponent == null) {
mainActivityComponent = appComponent.plusMainAcitvityComponent(MainActivityModule(activity))
}
return mainActivityComponent
}
fun clearMainActivityComponent(){
mainActivityComponent = null
}
}
When I run the app I am getting a internal compiler error. Below are my IDE logs:
2017-11-29 11:54:39,953 [J pool 2/4] WARN - hes.resolve.KotlinCacheService - Could not find correct module information.
Reason: Analyzing element of type class com.android.tools.idea.databinding.LightGeneratedComponentClass with no containing file
Text:
null
2017-11-29 11:54:39,953 [J pool 2/4] WARN - .resolve.jvm.JvmAnalyzerFacade - Java referenced null from LibraryInfo(libraryName=com.android.databinding:library-1.3.1)
Referenced class was: JavaClassImpl: DATA binding component class
NOTE: MainActivityComponent is a SubComponent of ApplicationComponent. Even though ApplicationComponent has only one module at present, Further modules will be added in the future
I can't really understand the error message as of now. There are two things that I can see which can make it go wrong here
1) To add a subcomponent to a parent component, add the subcomponent class to the subcomponents attribute of a #Module that the parent component installs. Then the subcomponent’s builder can be requested from within the parent
#Module(subcomponents = XYZ.class)
class AppModule(val app: Application){
2) Also, you are providing the same scope to subcomponent and component which is not allowed unless there is no ambiguity in resolution.
Please read the documentation here at
https://google.github.io/dagger/subcomponents.html
Your error has nothing to do with Dagger, but with Android's data binding library. Are you using that? If not, remove it from your dependencies and try again?

How can i inject object into presenter in android kotlin MVP mosby app with dagger

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)
}

Categories

Resources