Search code examples
androiddagger-2dagger

Trying to solve a dependency cycle using dagger


dagger-android 2.16

I have a dependency cycle error in my Dagger module. I think I know what the problem is but not sure how to solve it.

This is the error message:

Found a dependency cycle:
  public interface LoginFragmentSubcomponent extends AndroidInjector<LoginFragment> {
     presentation.login.request.LoginRequest is injected at
              mobileui.login.di.LoginActivityModule.provideLoginResponseListener(…, loginRequest)
          presentation.login.response.LoginResponseListener is injected at
              mobileui.login.di.LoginActivityModule.provideLoginRequest(…, loginPresenter)
          presentation.login.request.LoginRequest is injected at
              mobileui.login.di.LoginActivityModule.provideLoginPresenter(…, loginRequest)
          mobileui.login.LoginPresenter is injected at
              mobileui.login.LoginFragment.loginPresenter

This is the module below where I am getting the error

@Module
class LoginActivityModule {
    @Reusable
    @Provides
    fun provideLoginPresenter(loginRequest: LoginRequest): LoginPresenter {
        return LoginPresenterImp(loginRequest)
    }

    @Reusable
    @Provides
    fun provideLoginResponseListener(loginRequest: LoginRequest): LoginResponseListener {
        LoginPresenterImp(loginRequest)
    }

    @Reusable
    @Provides
    fun provideLoginRequest(loginUser: LoginUser,
                            loginPresenter: LoginResponseListener): LoginRequest {
        return LoginRequestImp(loginUser, loginPresenter)
    }
}

My LoginPresenterImp implements the LoginResponseListener and I want to pass that to the LoginRequestImp class so I can use it as a callback.

class LoginPresenterImp(private val loginRequest: LoginRequest) :
    BasePresenterImp<LoginView>(),
    LoginPresenter,
    LoginResponseListener {
}

And the loginResponseListener gets passed here:

class LoginRequestImp(
    private val loginUser: LoginUser,
    private val loginResponseListener: LoginResponseListener)
    : LoginRequest {
}

Many thanks in advance,


Solution

  • As Ayush described in the comments:

    You need LoginResponseListener to create LoginRequest and you need LoginRequest to create LoginResponseListener. So, you are getting the error.

    When you are creating LoginRequest in LoginRequestImp(loginUser, loginPresenter), loginPresenter is a parameter to the constructor of type LoginResponseListener. You should try to eliminate this dependency. May be you can set the listener later from the presenter

    In your reply between those comments:

    LoginRequest has been created in provideLoginRequest

    But this is what's happening:

    1. Your LoginFragment tries to inject LoginPresenter.
    2. Before you inject LoginPresenter you need to create a LoginRequest.
    3. Before you create a LoginRequest you need a LoginUser and a LoginRequestListener.
    4. Before you create a LoginRequestListener (which you've implemented as a LoginPresenterImpl), you need a LoginRequest.
    5. You're in the middle of creating a LoginRequest, so Dagger gives up and correctly reports a circular reference.

    To reiterate: Even though you've set up the bindings using interfaces correctly, Dagger can't create any of them because to call either constructor it has to create the other. This isn't a problem with Dagger: If class A's constructor takes an instance of B, and class B's constructor takes an instance of A, you couldn't construct either of them manually either while respecting their constructor parameters.


    As Ayush suggested, don't have LoginRequest inject a LoginResponseListener. Instead, create a method like setLoginResponseListener, which LoginPresenterImp can call. I recommend this approach as well, in part because @Reusable has weaker semantics than you want: You want to be absolutely sure that the LoginPresenterImp instance that acts as your LoginPresenter is the same instance that acts as LoginResponseListener.

    As an alternative, you can inject Provider<LoginPresenter> instead of LoginResponseListener, and change LoginRequestImp to accept a Provider as well. (You could also inject a Provider<LoginResponseListener>, but if you want that LoginResponseListener to be the same one as your LoginPresenter instance, you shouldn't call the LoginPresenterImp constructor explicitly. You'd want to switch to @Binds ideally, or at least have your @Provides method inject LoginPresenter instead.) You're allowed to inject a Provider, because a Provider<T> is automatically bound for every class <T> that Dagger knows how to provide, and it solves your problem because Dagger can pass a Provider<T> without trying to create the T. This will technically appear to work even if you leave your bindings as @Reusable, but in a multithreaded environment @Reusable isn't going to guarantee that you always receive the same instance for LoginRequestListener as your LoginPresenter, or that you'll receive a new LoginPresenter for every LoginFragment. If you want to guarantee that, you can look into custom scopes.