Dagger 2 - Error while providing a dependency - android

I'm really new with Dagger 2, I know how it works and what it does, but I'm getting some problems trying to implement it into my project.
My objective for now, is just inject the presenter into my view, the goal is to decouple my view of doing
presenter = Presenter(myInteractor())
This is what I have tried
MyAppApplication
class MyAppApplication: Application() {
lateinit var presentationComponent: PresentationComponent
override fun onCreate() {
super.onCreate()
createPresentationComponent()
}
private fun createPresentationComponent() {
presentationComponent = DaggerPresentationComponent.builder()
.presentationModule(PresentationModule(this))
.build()
}
}
PresentationComponent
#Component(modules = arrayOf(PresentationModule::class))
#Singleton
interface PresentationComponent {
fun inject(loginActivity: LoginView)
fun loginUserPresenter(): LoginPresenter
}
PresentationModule
#Module
class PresentationModule(application: Application) {
#Provides #Singleton fun provideLoginUserPresenter(signInInteractor: SignInInteractor): LoginPresenter {
return LoginPresenter(signInInteractor)
}
}
SignInInteractor
interface SignInInteractor {
interface SignInCallBack {
fun onSuccess()
fun onFailure(errormsg:String)
}
fun signInWithEmailAndPassword(email:String,password:String,listener:SignInCallBack)
fun firebaseAuthWithGoogle(account: GoogleSignInAccount, listener:SignInCallBack)
}
Now, I thinked that this is all I needed to inject the interactor into my presenter without any problems and then inject the presenter inside my view, but is giving me this error
error: [Dagger/MissingBinding] com.myapp.domain.interactor.logininteractor.SignInInteractor cannot be provided without an #Provides-annotated method.
I'm kinda confused because If I just provide the presentationModule that is responsible to bind my signInInteractor into my Presenter, it should be working, but is not.
Thanks in advance for any help

It's as the error message says, you're trying to pass a SignInInteractor in your PresentationModule to your LoginPresenter, yet you're not providing an implementation for it anywhere. A possible solution would be to add the following block of code to your PresentationModule:
#Provides #Singleton fun provideSignInInteractor(): SignInInteractor {
return TODO("Add an implementation of SignInInteractor here.")
}
Of course the TODO needs to be replaced by a SignInInteractor of your choosing (the myInteractor() function would work for example). Then that SignInInteractor will be used by your LoginPresenter. Hope that helps!

Related

Using Dagger with Espresso

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?

Dagger 2 MissingBinding when swapping concretion for interface

I have two classes that I'm able to have Dagger find and inject for me to use successfully:
TrackEvent
class TrackEvent #Inject constructor(
private val getTrackingProperties: SomeClass
) : UseCase<Boolean, TrackingEvent> {
override suspend operator fun invoke(params: TrackingEvent): Boolean {
return true
}
SomeClass (note: used as a dependency in TrackEvent)
class SomeClass #Inject constructor() {
override suspend operator fun invoke(): UserTrackingPropertiesResult {
return UserTrackingPropertiesResult()
}
}
TrackEvent has an entry in an #Module annotated interface because it's an implementation of the UseCase interface:
#Component(modules = [MyModule::class])
interface ShiftsComponent {
fun inject(homeFragment: HomeFragment)
}
#Module
interface MyModule {
#Binds
fun bindsTrackEventUseCase(useCase: TrackEvent): UseCase<Boolean, TrackingEvent>
}
Use Case interfaces
interface UseCase<out T, in P> {
suspend operator fun invoke(params: P): T
}
interface NoParamUseCase<out T> {
suspend operator fun invoke(): T
}
What I'd like to do is to inject an interface into TrackEvent instead of the concrete SomeClass. So I make SomeClass implement a NoParamUseCase
class SomeClass #Inject constructor(): NoParamUseCase<UserTrackingPropertiesResult> {
override suspend operator fun invoke(): UserTrackingPropertiesResult {
return UserTrackingPropertiesResult()
}
}
update TrackEvent to inject the interface:
class TrackEvent #Inject constructor(
private val getTrackingProperties: NoParamUseCase<UserTrackingPropertiesResult>) : UseCase<Boolean, TrackingEvent> {
override suspend operator fun invoke(params: TrackingEvent): Boolean {
return true
}
}
…and update MyModule to inform Dagger of which implementation I'd like to use:
#Module
interface MyModule {
#Binds
fun bindsTrackEventUseCase(useCase: TrackEvent): UseCase<Boolean, TrackingEvent>
// New
#Binds
fun bindsSomeClass(useCase: SomeClass): NoParamUseCase<UserTrackingPropertiesResult>
}
Dagger now claims that there is a missing binding and that I need to declare an #Provides annotated method:
error: [Dagger/MissingBinding] com.myapp.core.domain.usecase.NoParamUseCase<? extends com.myapp.core.tracking.UserTrackingPropertiesResult> cannot be provided without an #Provides-annotated method.
public abstract interface MyComponent {
^
com.myapp.core.domain.usecase.NoParamUseCase<? extends com.myapp.core.tracking.UserTrackingPropertiesResult> is injected at
com.myapp.tasks.tracking.domain.usecase.TrackEvent(getTrackingProperties, …)
…
As far as I can tell, this isn't true:
While, I've opted for #Binds in this instance, replacing this with #Provides and manually providing dependencies here yields the same error.
I'm using the exact same approach for the TrackEvent class and this works.
The only thing I've changed is that I'd like to provide an interface instead. I'd fully understand this error had I not provided the #Binds declaration.
This is different to this question as there's no ambiguity as to which implementation I'm asking Dagger to use in the way that there would be if I had two or more implementations of the same interface.
Why would I get this error now?
According to dagger error message, it expects covariant type NoParamUseCase<? extends UserTrackingPropertiesResult>, but DI module provides invariant NoParamUseCase<UserTrackingPropertiesResult>. To generate appropriate signature for provide method you can change it like this:
#Binds fun bindsSomeClass(useCase: SomeClass): NoParamUseCase<#JvmWildcard UserTrackingPropertiesResult>
After that your code should be compiled successfully.

Not able to inject a class using SubComponent in Dagger 2

I'm trying to add Dagger 2 in one project. I have created 3 components
AppComponent (the primary one)
RetrofitComponent (Dependent component works fine)
ControllerComponent (Subcomponent, not injecting properly)
this is my AppComponent
#ApplicationScope
#Component(modules = [ApplicationModule::class, PrefModule::class])
interface AppComponent {
fun inject(instance: AppInstance)
fun getAppPref(): AppPreference
fun newControllerComponent(controllerModule: ControllerModule): ControllerComponent
}
this is my ControllerComponent
#UIScope
#Subcomponent(modules = [ControllerModule::class])
interface ControllerComponent {
fun injectActivity(activity: BaseActivity)
fun injectDialog(dialog: BaseDialog)
}
ControllerModule
#Module
class ControllerModule(activity: FragmentActivity) {
var mActivity: FragmentActivity
init {
mActivity = activity
}
#Provides
fun getContext(): Context {
return mActivity
}
#Provides
fun getActivity(): Activity {
return mActivity
}
#Provides
fun getFragmentManager(): FragmentManager {
return mActivity.supportFragmentManager
}
#Provides
fun getDialogManager(fragmentManager: FragmentManager): DialogsManager
{
return DialogsManager(fragmentManager)
}
#Provides
fun getDialogsFactory(): DialogsFactory {
return DialogsFactory()
}
}
Injecting the activity is working fine, but when I try to inject the Dialog its never injecting
controller.injectDialog(singleDialog)
My entire code is here Github link. Need some help. What am I not doing or doing wrong that I can't inject from subcomponent.
To use field injection on a given class, you need to specify the concrete type rather than its superclass.
#UIScope
#Subcomponent(modules = [ControllerModule::class])
interface ControllerComponent {
//fun injectActivity(activity: BaseActivity)
//fun injectDialog(dialog: BaseDialog)
fun inject(activity: SomeSpecificActivity)
fun inject(activity: SomeOtherActivity)
fun inject(dialog: SomeSpecificDialog)
fun inject(dialog: SomeOtherDialog)
}
The Injection is working, but you are injecting nothing inside SingleClickDialog class, remember than the method inject(class : Class) is for let it to know dagger that class will be injected, but you need to put the elements you need inject inside that class with the annotation #Inject, and those elements you need to add its provider in ControllerModule as well

Can't inject same instance into a Service and a ViewModel

I am trying to replicate this this Singleton using Dagger 2.
I would like to have a BehaviorSubject in which I call onNext() to in a service and subscribe() to in a ViewModel.
Here is my Singleton object:
object MyMessageBus {
val pusher = BehaviorSubject.create<String>()
}
In my Service I can do this:
private val pusherObservable = MyMessageBus.pusher
override fun onCreate() {
super.onCreate()
println("service myObservable = ${pusherObservable.hashCode()}")
}
and then in my ViewModel I can do this:
private val pusherObservable = MyMessageBus.pusher
init {
println("viewModel myObservable = ${pusherObservable.hashCode()}")
}
When I run with this I get the same observable in both the Service and the ViewModel.
viewModel myObservable = 218654319
service myObservable = 218654319
and so I can call onNext in the Service and observe the change in the ViewModel.
As I said above I am trying to replicate this using Dagger, but I cant seem to get the same instance in both the Service and the ViewModel. I think the problem is that I just have two different graphs, but I cant figure out how to join them - even after reading the docs and knowing about Subcomponents.
Besides that, this seems like a huge amount of code to do something so simple.
So my question is how do I do the equivalent using Dagger with the minimum amount of code?
Here is what I have tried so far:
Here is the class I would like to inject into the Service and ViewModel.
Note: I originally thought just annotating the class and constructor would work, but have also tried with a module (hence the comments)
//#Singleton
class MessageBus {
val pusher: BehaviorSubject<String>
// #Inject
constructor() {
pusher = BehaviorSubject.create<String>()
}
}
Here is the module for providing the Service injections
#Singleton
#Module()
abstract class AndroidModule {
#ContributesAndroidInjector
abstract fun contributesPusherService(): PusherService
}
Here is the module that provides the MessageBus (Note I also tried just using commented out annoations above.
#Module
class PervasiveModule {
#Provides #Singleton fun provideMessageBus() = MessageBus()
}
Next is the App component
#Singleton
#Component(modules = arrayOf(AndroidInjectionModule::class, AndroidModule::class, PervasiveModule::class))
interface AppComponent {
#Component.Builder
interface Builder {
#BindsInstance
fun application(myApp: MyApp): Builder
fun build(): AppComponent
}
fun inject(myApp: MyApp): MyApp
}
finally the component for the ViewModel.
#Component(modules = arrayOf(PervasiveModule::class))
#Singleton
interface MainViewModelComponent {
#Component.Builder
interface Builder {
fun build(): MainViewModelComponent
}
fun inject(mainViewModel: MainViewModel)
}
In the service I have:
#Inject lateinit var messageBus: MessageBus
override fun onCreate() {
AndroidInjection.inject(this)
super.onCreate()
setupPusher()
println("service created")
println("service messageBus = ${messageBus.hashCode()}")
}
In the ViewModel I have:
#Inject lateinit var messageBus: MessageBus
init {
DaggerMainViewModelComponent.create().inject(this)
println("view model messageBus = ${messageBus.hashCode()}")
}
When I run this, everything is injected, but I end up with two instances of MessageBus rather than one.
view model messageBus = 252114254
service messageBus = 91479273
As I said above I think the issue is that MainViewModelComponent and the AppComponent are actually two different graphs. Is this the case? If so how do I join them. If not can someone explain what's going on and how to get this to work.

Dagger 2 + Kotlin can't inject Presenter into View

I am trying to create simple MVP Archtecture app using Dagger 2. I am tying to achieave same result as in this tutorial, but with Kotlin. Here is my code so far.
Presenter:
class MainPresenter #Inject constructor(var view: IMainView): IMainPresenter{
override fun beginMessuring() {
view.toastMessage("Measuring started")
}
override fun stopMessuring() {
view.toastMessage("Measuring stopped")
}
}
View:
class MainActivity : AppCompatActivity(), IMainView {
#Inject lateinit var presenter : MainPresenter
val component: IMainComponent by lazy {
DaggerIMainComponent
.builder()
.mainPresenterModule(MainPresenterModule(this))
.build()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
component.inject(this)
presenter.beginMessuring()
}
override fun toastMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
}
}
Dagger Module:
#Module
class MainPresenterModule(private val view: IMainView) {
#Provides
fun provideView() = view
}
Dagger Component:
#Component(modules = arrayOf(MainPresenterModule::class))
interface IMainComponent {
fun inject(mainView : IMainActivity)
}
The problem is that I am getting build error which starts with this:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.maciej.spiritlvl/com.example.maciej.spiritlvl.View.MainActivity}: kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
PS, my gradle dagger config:
kapt 'com.google.dagger:dagger-compiler:2.9'
mplementation 'com.google.dagger:dagger:2.9'
EDIT:
Changed injected presenter type from IMainView to MainView.
Whenever trying to inject any interface, like in your case IMainPresenter, you need to tell dagger which concrete implementation to use. Dagger has no means of knowing which implementation of that interface you want to 'have' (you might have numerous implementations of that interface).
You did the right thing for the IMainView by adding a #Provides-annotated method to your module. You can do the same for your presenter, but that imho would render the whole point of dagger useless, because you'd have to create the presenter yourself when creating the module.
So I would, instead of injecting the IMainPresenter interface into your activity, inject the concrete implementation MainPresenter. Then you also shouldn't need a #Provides method in your module (for the presenter).

Categories

Resources