1

I am getting a dependency cycle whenever I try to use a subcomponent with binding objects. I have an app scope and an activity scope. At the app scope I create my web service then when the activity opens I want to create a storage object, controller, and navigator (all custom classes not androidx classes) and inject them into my androidx ViewModel class. But when I do so I get a dependency cycle.

My top level component looks like

@AppScope
@Component(modules = [AppModule::class])
interface AppComponent {

  val activityComponentBuilder: ActivityComponent.Builder
}

@Module(subcomponents = [ActivityComponent::class])
interface AppModule {

  @Binds
  fun mockWebService(mockWebService: MockWebService): MockWebService
}

Next my subcomponent looks like

@ActivityComponent
@Subcomponent(modules = [ActivityModule::class])
interface ActivityComponent {

  fun inject(sharedViewModel: SharedViewModel)

  @Subcomponent.Builder
  interface Builder {
    @BindsInstance
    fun storage(storage: Storage): Builder

    fun build(): ActivityComponent
  }
}

In my activity module I bind two objects

  @Binds
  abstract fun controller(controller: Controller): Controller

  @Binds
  abstract fun navigator(navigator: Navigator): Navigator

Each object has an @Inject constructor

class Navigator @Inject constructor(private val storage: Storage)

class Controller @Inject constructor(
  private val webService: MockWebService,
  private val navigator: Navigator,
  private val storage: Storage
) {

Inside my shared view model I try to build my component and inject the fields

  @Inject
  lateinit var navigator: Navigator
  @Inject
  lateinit var controller: Controller

  init {
      MainApplication.component.activityComponentBuilder
        .storage(InMemoryStorage.from(UUID.randomUUID().toString()))
        .build()
        .inject(this)

  }

But dagger won't build. I get an error

[Dagger/DependencyCycle] Found a dependency cycle: public abstract interface AppComponent {

MockWebService is injected at di.AppModule.mockWebService(mockWebService)
MockWebService is injected at ActivityModule.Controller(webService, …)
Controller is injected at SharedViewModel.controller
SharedViewModel is injected at

But the error message cuts off there. Am I missing something in how to use a subcomponent to put objects on the graph and then inject them into an object? Is this not possible with Dagger?

Zachary Sweigart
  • 1,051
  • 9
  • 23
  • 1
    `@Binds` is used to let dagger know the different implementations of an interface. You don't need `@binds` here since `Navigator` and `Controller` are normal classes that do not implement any interface. I'd assume that's the case with `MockWebService` too. Also, those classes have `@Inject constructor`, which means dagger can instantiate them and you don't need to write `@Provides` too – denvercoder9 May 29 '20 at 01:38
  • But without `@Binds` will the `MockWebService` be scoped to the application scope? – Zachary Sweigart May 29 '20 at 01:40
  • Can you post the class declration of `MockWebService`? – denvercoder9 May 29 '20 at 01:42
  • 1
    `@Binds` isn't doing any scoping. Its only job is to tell dagger about different implementations. You can add `@XScope` with `@Binds` to make some object scoped. Or, you could just add the scope annotation to the class declaration. – denvercoder9 May 29 '20 at 01:46
  • Oh you can add an `@Scope` annotation on an `@Inject` constructor? `MockWebService` is currently an object with an empty constructor so nothing interesting there – Zachary Sweigart May 29 '20 at 01:50
  • 1
    Not on the constructor but on the class. See an example [here](https://stackoverflow.com/a/58009976/2235972). As for the dependency cycle, I think it's because you're telling `ActivityComponent` to use `ActivityModule` and telling `ActivityModule` to install `ActivityComponent`. Doing just either one should be the case (I think) – denvercoder9 May 29 '20 at 02:05
  • Worked like a charm! Thanks! – Zachary Sweigart May 29 '20 at 02:17
  • 1
    I'll add this as an answer for future readers who will stumble upon this – denvercoder9 May 29 '20 at 02:25

1 Answers1

2

@Binds is used to let dagger know the different implementations of an interface. You don't need @Binds here since Navigator and Controller are simple classes that do not implement any interface. I'd assume that's the case with MockWebService too. Also, those classes have @Inject constructor, which means dagger can instantiate them and we don't need to write extra @Provides functions for those classes.

@Binds isn't doing any scoping. Its only job is to tell dagger about different implementations. You can add @XScope with @Binds to make some object scoped. Or, you could just add the scope annotation to the class declaration. Here's an example of how you can add scope to class declaration.

As for the dependency cycle, I think it's because you're telling ActivityComponent to use ActivityModule and telling ActivityModule to install ActivityComponent. Doing just either one should be the case (I think).

denvercoder9
  • 2,979
  • 3
  • 28
  • 41