Home > Software design >  Kotlin Hilt: Dependency cycle providing one dependence that has many other dependencies
Kotlin Hilt: Dependency cycle providing one dependence that has many other dependencies

Time:09-27

I have a 'DownloadTests' class that has many dependencies:

class DownloadTests
@Inject constructor(
     val awsTestTypeService: IAWSTestTypeService,
     val awsTestSubjectService: IAWSTestSubjectService,
     val awsTestService: IAWSTestService,
     val imageService: IImageService,
     val s3Storage: IS3StorageService,
     val testService: ITestService,
     val testTypeService: ITestTypeService,
     val testSubjectService: ITestSubjectService,
     val questionService: IQuestionService,
     val answerService: IAnswerService
) {
...

And some of the 'DownloadTests' class dependencies at the same time have other dependencies, like for example:

class AWSTestTypeService
    @Inject constructor(
        private val awsTestTypeRepository: AWSTestTypeRepository
    ) : IAWSTestTypeService {

    override fun getTestTypes(): List<TestTypeDTO>?
    {
        return awsTestTypeRepository.getTestTypes()
    }
}

(I won't paste every 'DownloadTests' dependency because I don't consider it necessary)

Said that, I first tried providing 'DownloadTests' for classes that need it in the next way:

@Provides
@Singleton
fun provideDownloadTests(downloadTests: DownloadTests): DownloadTests{
    return downloadTests
}

But this leads to a 'Dependency Cycle' building error:

/Users/xxx/StudioProjects/xxx/app/build/generated/hilt/component_sources/debug/com/xxx/xxx/ui/app/TMApplication_HiltComponents.java:165: error: [Dagger/DependencyCycle] Found a dependency cycle:
  public abstract static class SingletonC implements xxx.xxx.xxx.common.dependencies.Dependencies.IEntryPoint,
                         ^
      xxx.xxx.xxx.background.downloadtests.DownloadTests is injected at
          xxx.xxx.xxx.background.dependencies.Dependencies.provideDownloadTests(downloadTests)
      xxx.xxx.xxx.background.downloadtests.DownloadTests is injected at
          xxx.xxx.xxx.background.dependencies.Dependencies.provideDownloadTests(downloadTests)
      ...
  
  The cycle is requested via:
      xxx.xxx.xxx.background.downloadtests.DownloadTests is injected at
          xxx.xxx.xxx.ui.activities.shared.BaseActivity.downloadTests
      xxx.xxx.xxx.ui.activities.community.khan.KhanActivity is injected at
          xxx.xxx.xxx.ui.activities.community.khan.KhanActivity_GeneratedInjector.injectKhanActivity(xxx.xxx.xxx.ui.activities.community.khan.KhanActivity) [xxx.xxx.xxx.ui.app.TMApplication_HiltComponents.SingletonC → xxx.xxx.xxx.ui.app.TMApplication_HiltComponents.ActivityRetainedC → xxx.xxx.xxx.ui.app.TMApplication_HiltComponents.ActivityC]

So, even I'm not convinced -and I'm not even sure it's the right way- I tried:

@Provides
@Singleton
fun provideDownloadTests(): DownloadTests{
    return DownloadTests(
        AWSTestTypeService(AWSTestTypeRepository()),
        AWSTestSubjectService(AWSTestSubjectRepository()),
        AWSTestService(AWSTestRepository()),
        ImageService(ImageRepository()),
        S3StorageService(),
        TestService(AWSTestRepository(), SQLiteHelper()),
        TestTypeService(),
        TestSubjectService(),
        QuestionService(),
        AnswerService())
}

And to my surprise, this second way it just builds -and works- fine.

In this second approach I'm providing the classes instances myself, but it's too much code and I think it's Hilt responsibility to do that.

In resume, should I go with the first approach? If so, how to deal with the dependency cycle error? If not, is my second approach really correct?

CodePudding user response:

I would suggest going with the second one. First of all the first one is unscoped while the second one is singleton scoped.

The first approach you took is wrong since it creats a dependency cycle. Look at the graph below:

enter image description here

The second approach can be tweaked a little. Since you have already created DI for various classes Hilt using @Inject constructor and inject them into provideDownloadTests () you can mention parameters in constructor which are required. For Example:

    @Provides
    @ActivityRetainedScoped
    fun provideSomeClass(
        application: Application,
        b: B,
        c: C,
        d: Lazy<D>,
        e: Lazy<E>,
        f: F,
        g: G
    ): SomeClass {
        return SomeClass(application, b, c, d, e, f, g)
    }

PS: I maybe be wrong. I started with DI just a couple of weeks back.

  • Related